Основы Android Kotlin 08.2: Загрузка и отображение изображений из Интернета

Эта практическая работа входит в курс «Основы Android Kotlin». Вы получите максимальную пользу от этого курса, если будете выполнять практические работы последовательно. Все практические работы курса перечислены на целевой странице практической работы «Основы Android Kotlin» .

Введение

В предыдущей работе вы узнали, как получать данные из веб-сервиса и преобразовывать ответ в объект данных. В этой работе вы, используя эти знания, загрузите и отобразите фотографии с веб-адреса. Вы также повторите, как создать RecyclerView и использовать его для отображения сетки изображений на странице обзора.

Что вам уже следует знать

  • Как создавать и использовать фрагменты.
  • Как использовать компоненты архитектуры, включая модели представлений, фабрики моделей представлений, преобразования и LiveData .
  • Как извлечь JSON из веб-сервиса REST и преобразовать эти данные в объекты Kotlin с помощью библиотек Retrofit и Moshi .
  • Как построить сетку с помощью RecyclerView .
  • Как работают Adapter , ViewHolder и DiffUtil .

Чему вы научитесь

  • Как использовать библиотеку Glide для загрузки и отображения изображения с веб-URL.
  • Как использовать RecyclerView и адаптер сетки для отображения сетки изображений.
  • Как обрабатывать потенциальные ошибки при загрузке и отображении изображений.

Что ты будешь делать?

  • Измените приложение MarsRealEstate так, чтобы оно получало URL-адрес изображения из данных о недвижимости Mars, и используйте Glide для загрузки и отображения этого изображения.
  • Добавьте в приложение анимацию загрузки и значок ошибки.
  • Используйте RecyclerView для отображения сетки изображений объектов недвижимости на Марсе.
  • Добавьте статус и обработку ошибок в RecyclerView .

В этой практической работе (и связанных с ней практических работах) вы будете работать с приложением MarsRealEstate, которое показывает недвижимость, выставленную на продажу на Марсе. Приложение подключается к интернет-серверу для получения и отображения данных о недвижимости, включая такие сведения, как цена и доступность недвижимости для продажи или аренды. Изображения каждого объекта недвижимости — это реальные фотографии с Марса, сделанные марсоходами NASA.

Версия приложения, которую вы создадите в этой лабораторной работе, заполняет страницу обзора, отображающую сетку изображений. Изображения являются частью данных о недвижимости, которые ваше приложение получает из веб-сервиса недвижимости Mars. Ваше приложение будет использовать библиотеку Glide для загрузки и отображения изображений, а также RecyclerView для создания сетки изображений. Ваше приложение также будет корректно обрабатывать сетевые ошибки.

Отображение фотографии с веб-ссылки может показаться простым, но для его корректной работы требуется немало инженерных решений. Изображение необходимо загрузить, буферизировать и декодировать из сжатого формата в формат, подходящий для Android. Изображение должно быть кэшировано в памяти, в хранилище или в обоих. Всё это должно происходить в низкоприоритетных фоновых потоках, чтобы пользовательский интерфейс оставался отзывчивым. Кроме того, для оптимальной производительности сети и процессора может потребоваться загрузка и декодирование нескольких изображений одновременно. Изучение того, как эффективно загружать изображения из сети, само по себе может стать отдельной лабораторной работой.

К счастью, вы можете использовать разработанную сообществом библиотеку Glide для загрузки, буферизации, декодирования и кэширования изображений. Glide значительно упрощает задачу, чем если бы вам пришлось делать всё это с нуля.

По сути, Glide нуждается в двух вещах:

  • URL-адрес изображения, которое вы хотите загрузить и отобразить.
  • Объект ImageView для отображения этого изображения.

В этом задании вы научитесь использовать Glide для отображения одного изображения из веб-сервиса недвижимости. Вы отображаете изображение, представляющее первый объект недвижимости на Марсе в списке объектов, возвращаемых веб-сервисом. Вот скриншоты «до» и «после»:

Шаг 1: Добавьте зависимость Glide

  1. Откройте приложение MarsRealEstate из последнего практического занятия. (Если у вас нет приложения, вы можете скачать MarsRealEstateNetwork здесь.)
  2. Запустите приложение и посмотрите, что оно делает. (Оно отображает текстовую информацию о недвижимости, которая предположительно доступна на Марсе.)
  3. Откройте build.gradle (Модуль: app) .
  4. В разделе dependencies добавьте эту строку для библиотеки Glide:
implementation "com.github.bumptech.glide:glide:$version_glide"


Обратите внимание, что номер версии уже определен отдельно в файле Gradle проекта.

  1. Нажмите «Синхронизировать сейчас» , чтобы перестроить проект с новой зависимостью.

Шаг 2: Обновите модель представления

Затем вы обновляете класс OverviewViewModel , чтобы включить в него актуальные данные для одного свойства Mars.

  1. Откройте overview/OverviewViewModel.kt . Чуть ниже LiveData для _response добавьте внутренние (изменяемые) и внешние (неизменяемые) данные Live для одного объекта MarsProperty .

    При необходимости импортируйте класс MarsProperty ( com.example.android.marsrealestate.network.MarsProperty ).
private val _property = MutableLiveData<MarsProperty>()

val property: LiveData<MarsProperty>
   get() = _property
  1. В методе getMarsRealEstateProperties() найдите строку внутри блока try/catch {} , которая устанавливает значение _response.value равным количеству свойств. Добавьте показанный ниже тест. Если доступны объекты MarsProperty , этот тест устанавливает значение _property LiveData равным первому свойству в listResult .
if (listResult.size > 0) {   
    _property.value = listResult[0]
}

Полный блок try/catch {} теперь выглядит так:

try {
   var listResult = getPropertiesDeferred.await()
   _response.value = "Success: ${listResult.size} Mars properties retrieved"
   if (listResult.size > 0) {      
       _property.value = listResult[0]
   }
 } catch (e: Exception) {
    _response.value = "Failure: ${e.message}"
 }
  1. Откройте файл res/layout/fragment_overview.xml . В элементе <TextView> измените android:text для привязки к компоненту imgSrcUrl property LiveData :
android:text="@{viewModel.property.imgSrcUrl}"
  1. Запустите приложение. TextView отображает только URL-адрес изображения в первом свойстве Mars. Всё, что вы сделали до сих пор, — это настроили модель представления и актуальные данные для этого URL-адреса.

Шаг 3: Создайте адаптер привязки и вызовите Glide

Теперь у вас есть URL-адрес изображения для отображения, и пришло время начать работу с Glide для его загрузки. На этом этапе вы используете адаптер привязки для получения URL-адреса из XML-атрибута, связанного с ImageView , и используете Glide для загрузки изображения. Адаптеры привязки — это методы расширения, которые располагаются между представлением и привязанными данными, обеспечивая настраиваемое поведение при изменении данных. В данном случае настраиваемое поведение заключается в вызове Glide для загрузки изображения из URL-адреса в ImageView .

  1. Откройте BindingAdapters.kt . Этот файл будет содержать адаптеры привязки, которые вы используете в приложении.
  2. Создайте функцию bindImage() , принимающую в качестве параметров ImageView и String . Добавьте к функции аннотацию @BindingAdapter . Эта аннотация сообщает привязке данных, что этот адаптер привязки должен выполняться @BindingAdapter когда XML-элемент имеет атрибут imageUrl .

    Импортируйте androidx.databinding.BindingAdapter и android.widget.ImageView по запросу.
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {

}
  1. Внутри функции bindImage() добавьте блок let {} для аргумента imgUrl :
imgUrl?.let { 
}
  1. Внутри блока let {} добавьте строку, показанную ниже, чтобы преобразовать строку URL (из XML) в объект Uri . Импортируйте androidx.core.net.toUri по запросу.

    Вам нужно, чтобы конечный объект Uri использовал схему HTTPS, поскольку сервер, с которого вы извлекаете изображения, требует именно эту схему. Чтобы использовать схему HTTPS, добавьте buildUpon.scheme("https") к конструктору toUri . Метод toUri() — это функция расширения Kotlin из базовой библиотеки Android KTX, поэтому он выглядит как часть класса String .
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
  1. Оставаясь внутри let {} , вызовите Glide.with() для загрузки изображения из объекта Uri в ImageView . Импортируйте com.bumptech.glide.Glide по запросу.
Glide.with(imgView.context)
       .load(imgUri)
       .into(imgView)

Шаг 4: Обновите макет и фрагменты

Хотя Glide загрузил изображение, пока ничего не видно. Следующий шаг — обновить макет и фрагменты с помощью ImageView для отображения изображения.

  1. Откройте res/layout/gridview_item.xml . Это файл ресурсов макета, который вы будете использовать для каждого элемента RecyclerView далее в этой лабораторной работе. Здесь он используется временно, чтобы показать только одно изображение.
  2. Над элементом <ImageView> добавьте элемент <data> для привязки данных и выполните привязку к классу OverviewViewModel :
<data>
   <variable
       name="viewModel"
       type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
  1. Добавьте атрибут app:imageUrl к элементу ImageView , чтобы использовать новый адаптер привязки загрузки изображений:
app:imageUrl="@{viewModel.property.imgSrcUrl}"
  1. Откройте overview/OverviewFragment.kt . В методе onCreateView() закомментируйте строку, которая расширяет класс FragmentOverviewBinding и присваивает его переменной привязки. Это временное изменение; вы вернётесь к нему позже.
//val binding = FragmentOverviewBinding.inflate(inflater)
  1. Вместо этого добавьте строку для расширения класса GridViewItemBinding . Импортируйте com.example.android.marsrealestate. databinding.GridViewItemBinding по запросу.
val binding = GridViewItemBinding.inflate(inflater)
  1. Запустите приложение. Теперь в списке результатов вы должны увидеть фотографию изображения из первого MarsProperty .

Шаг 5: Добавьте простые изображения загрузки и ошибок

Glide может улучшить пользовательский опыт, отображая изображение-заполнитель при загрузке изображения и изображение ошибки в случае сбоя загрузки, например, если изображение отсутствует или повреждено. На этом этапе вы добавляете эту функциональность в адаптер привязки и в макет.

  1. Откройте res/drawable/ic_broken_image.xml и перейдите на вкладку «Предварительный просмотр» справа. Для изображения ошибки используется значок «Broken Image» из встроенной библиотеки значков. Этот векторный объект использует атрибут android:tint для окрашивания значка в серый цвет.

  1. Откройте res/drawable/loading_animation.xml . Этот объект представляет собой анимацию, заданную тегом <animate-rotate> . Анимация вращает объект изображения loading_img.xml вокруг центральной точки. (В предварительном просмотре анимация не отображается.)

  1. Вернитесь к файлу BindingAdapters.kt . В методе bindImage() обновите вызов Glide.with() , чтобы вызвать функцию apply() между load() и into() . Импортируйте com.bumptech.glide.request.RequestOptions по запросу.

    Этот код задаёт загрузочное изображение-заглушку, которое будет использоваться при загрузке (рисунок loading_animation ). Код также задаёт изображение, которое будет использоваться в случае сбоя загрузки изображения (рисунок broken_image ). Полный метод bindImage() теперь выглядит следующим образом:
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
    imgUrl?.let {
        val imgUri = 
           imgUrl.toUri().buildUpon().scheme("https").build()
        Glide.with(imgView.context)
                .load(imgUri)
                .apply(RequestOptions()
                        .placeholder(R.drawable.loading_animation)
                        .error(R.drawable.ic_broken_image))
                .into(imgView)
    }
}
  1. Запустите приложение. В зависимости от скорости вашего сетевого соединения, вы можете на короткое время увидеть загрузочное изображение, пока Glide загружает и отображает изображение объекта. Но значок поврежденного изображения пока не будет виден, даже если вы отключите сеть — это будет исправлено в последней части работы.

Теперь ваше приложение загружает информацию о свойствах из Интернета. Используя данные из первого элемента списка MarsProperty , вы создали свойство LiveData в модели представления и использовали URL изображения из этого свойства для заполнения ImageView . Однако цель вашего приложения — отображать сетку изображений, поэтому вам нужно использовать RecyclerView с GridLayoutManager .

Шаг 1: Обновите модель представления

Сейчас в модели представления есть свойство LiveData _property , которое содержит один объект MarsProperty — первый в списке ответов веб-сервиса. На этом этапе вы изменяете это LiveData так, чтобы оно содержало весь список объектов MarsProperty .

  1. Откройте overview/OverviewViewModel.kt .
  2. Измените частную переменную _property на _properties . Измените тип на список объектов MarsProperty .
private val _properties = MutableLiveData<List<MarsProperty>>()
  1. Замените внешние property live data на properties . Добавьте список к типу LiveData здесь же:
 val properties: LiveData<List<MarsProperty>>
        get() = _properties
  1. Прокрутите вниз до метода getMarsRealEstateProperties() . Внутри блока try {} замените весь тест, добавленный в предыдущем задании, на строку, показанную ниже. Поскольку переменная listResult содержит список объектов MarsProperty , вы можете просто присвоить его _properties.value вместо проверки успешного ответа.
_properties.value = listResult

Весь блок try/catch теперь выглядит так:

try {
   var listResult = getPropertiesDeferred.await()
   _response.value = "Success: ${listResult.size} Mars properties retrieved"
   _properties.value = listResult
} catch (e: Exception) {
   _response.value = "Failure: ${e.message}"
}

Шаг 2: Обновите макеты и фрагменты

Следующим шагом будет изменение макета и фрагментов приложения для использования представления переработчика и макета сетки вместо представления одного изображения.

  1. Откройте res/layout/gridview_item.xml . Измените привязку данных с OverviewViewModel на MarsProperty и переименуйте переменную в "property" .
<variable
   name="property"
   type="com.example.android.marsrealestate.network.MarsProperty" />
  1. В <ImageView> измените атрибут app:imageUrl так, чтобы он ссылался на URL-адрес изображения в объекте MarsProperty :
app:imageUrl="@{property.imgSrcUrl}"
  1. Откройте overview/OverviewFragment.kt . В onCreateview() раскомментируйте строку, которая расширяет FragmentOverviewBinding . Удалите или закомментируйте строку, которая расширяет GridViewBinding . Эти изменения отменяют временные изменения, внесённые вами в предыдущей задаче.
val binding = FragmentOverviewBinding.inflate(inflater)
 // val binding = GridViewItemBinding.inflate(inflater)
  1. Откройте res/layout/fragment_overview.xml и удалите весь элемент <TextView> .
  2. Вместо этого добавьте этот элемент <RecyclerView> , который использует GridLayoutManager и макет grid_view_item для одного элемента:
<androidx.recyclerview.widget.RecyclerView
            android:id="@+id/photos_grid"
            android:layout_width="0dp"
            android:layout_height="0dp"
            android:padding="6dp"
            android:clipToPadding="false"
            app:layoutManager=
               "androidx.recyclerview.widget.GridLayoutManager"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:spanCount="2"
            tools:itemCount="16"
            tools:listitem="@layout/grid_view_item" />

Шаг 3: Добавьте адаптер фотосетки

Теперь макет fragment_overview содержит RecyclerView , а макет grid_view_item — один ImageView . На этом этапе данные привязываются к RecyclerView через адаптер RecyclerView .

  1. Откройте overview/PhotoGridAdapter.kt .
  2. Создайте класс PhotoGridAdapter с параметрами конструктора, показанными ниже. Класс PhotoGridAdapter расширяет ListAdapter , конструктору которого требуются тип элемента списка, держатель представления и реализация DiffUtil.ItemCallback .

    Импортируйте классы androidx.recyclerview.widget.ListAdapter и com.example.android.marsrealestate.network.MarsProperty по запросу. На следующих шагах вы реализуете другие отсутствующие части этого конструктора, которые приводят к ошибкам.
class PhotoGridAdapter : ListAdapter<MarsProperty,
        PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {

}
  1. Щелкните в любом месте класса PhotoGridAdapter и нажмите Control+i чтобы реализовать методы ListAdapter : onCreateViewHolder() и onBindViewHolder() .
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PhotoGridAdapter.MarsPropertyViewHolder {
   TODO("not implemented") 
}

override fun onBindViewHolder(holder: PhotoGridAdapter.MarsPropertyViewHolder, position: Int) {
   TODO("not implemented") 
}
  1. В конце определения класса PhotoGridAdapter , после только что добавленных методов, добавьте определение сопутствующего объекта для DiffCallback , как показано ниже.

    Импортируйте androidx.recyclerview.widget.DiffUtil по запросу.

    Объект DiffCallback расширяет DiffUtil.ItemCallback добавляя тип объекта, который вы хотите сравнить — MarsProperty .
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
  1. Нажмите Control+i , чтобы реализовать методы компаратора для этого объекта: areItemsTheSame() и areContentsTheSame() .
override fun areItemsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
   TODO("not implemented") 
}

override fun areContentsTheSame(oldItem: MarsProperty, newItem: MarsProperty): Boolean {
   TODO("not implemented") }
  1. Для метода areItemsTheSame() удалите TODO. Используйте оператор ссылочного равенства Kotlin ( === ), который возвращает true , если ссылки на объекты oldItem и newItem одинаковы.
override fun areItemsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem === newItem
}
  1. Для areContentsTheSame() используйте стандартный оператор равенства только для идентификаторов oldItem и newItem .
override fun areContentsTheSame(oldItem: MarsProperty, 
                  newItem: MarsProperty): Boolean {
   return oldItem.id == newItem.id
}
  1. Оставаясь внутри класса PhotoGridAdapter , под сопутствующим объектом добавьте определение внутреннего класса для MarsPropertyViewHolder , который расширяет RecyclerView.ViewHolder .

    Импортируйте androidx.recyclerview.widget.RecyclerView и com.example.android.marsrealestate.databinding.GridViewItemBinding по запросу.

    Для привязки MarsProperty к макету вам понадобится переменная GridViewItemBinding , поэтому передайте её в MarsPropertyViewHolder . Поскольку базовый класс ViewHolder требует представления в своём конструкторе, вы передаёте ему корневое представление привязки.
class MarsPropertyViewHolder(private var binding: 
                   GridViewItemBinding):
       RecyclerView.ViewHolder(binding.root) {

}
  1. В MarsPropertyViewHolder создайте метод bind() , который принимает объект MarsProperty в качестве аргумента и присваивает этому объекту binding.property . После установки свойства вызовите executePendingBindings() , что приведёт к немедленному выполнению обновления.
fun bind(marsProperty: MarsProperty) {
   binding.property = marsProperty
   binding.executePendingBindings()
}
  1. В onCreateViewHolder() удалите TODO и добавьте строку, показанную ниже. Импортируйте android.view.LayoutInflater по запросу.

    Метод onCreateViewHolder() должен возвращать новый MarsPropertyViewHolder , созданный путем расширения GridViewItemBinding и использования LayoutInflater из родительского контекста ViewGroup .
   return MarsPropertyViewHolder(GridViewItemBinding.inflate(
      LayoutInflater.from(parent.context)))
  1. В методе onBindViewHolder() удалите TODO и добавьте строки, показанные ниже. Здесь вы вызываете getItem() для получения объекта MarsProperty , связанного с текущей позицией RecyclerView , а затем передаёте это свойство методу bind() в MarsPropertyViewHolder .
val marsProperty = getItem(position)
holder.bind(marsProperty)

Шаг 4: Добавьте адаптер для крепления и соедините детали.

Наконец, используйте BindingAdapter для инициализации PhotoGridAdapter списком объектов MarsProperty . Использование BindingAdapter для установки данных RecyclerView приводит к тому, что привязка данных автоматически отслеживает LiveData для списка объектов MarsProperty . После этого адаптер привязки будет автоматически вызываться при изменении списка MarsProperty .

  1. Откройте BindingAdapters.kt .
  2. В конце файла добавьте метод bindRecyclerView() , который принимает RecyclerView и список объектов MarsProperty в качестве аргументов. Добавьте к этому методу аннотацию @BindingAdapter .

    Импортируйте androidx.recyclerview.widget.RecyclerView и com.example.android.marsrealestate.network.MarsProperty по запросу.
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView, 
    data: List<MarsProperty>?) {
}
  1. Внутри функции bindRecyclerView() приведите recyclerView.adapter к PhotoGridAdapter и вызовите adapter.submitList() с данными. Это сообщит RecyclerView о наличии нового списка.

Импортируйте com.example.android.marsrealestate.overview.PhotoGridAdapter по запросу.

val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
  1. Откройте res/layout/fragment_overview.xml . Добавьте атрибут app:listData к элементу RecyclerView и задайте ему значение viewmodel.properties , используя привязку данных.
app:listData="@{viewModel.properties}"
  1. Откройте overview/OverviewFragment.kt . В onCreateView() , непосредственно перед вызовом setHasOptionsMenu() , инициализируйте адаптер RecyclerView в binding.photosGrid для нового объекта PhotoGridAdapter .
binding.photosGrid.adapter = PhotoGridAdapter()
  1. Запустите приложение. Вы увидите сетку изображений MarsProperty . При прокрутке для просмотра новых изображений приложение отображает значок хода загрузки, а затем само изображение. Если включить режим полёта, незагруженные изображения будут отображаться как значки с повреждёнными изображениями.

Приложение MarsRealEstate отображает значок поврежденного изображения, когда его невозможно загрузить. Однако при отсутствии сети приложение отображает пустой экран.

Это не очень удобно для пользователя. В этой задаче вы добавляете базовую обработку ошибок, чтобы дать пользователю более четкое представление о происходящем. Если интернет отсутствует, приложение отображает значок ошибки подключения. Пока приложение получает список MarsProperty , отображается анимация загрузки.

Шаг 1: Добавьте статус в модель представления

Для начала создайте объект LiveData в модели представления для отображения статуса веб-запроса. Необходимо учитывать три состояния: загрузка, успешное выполнение и сбой. Состояние загрузки происходит во время ожидания данных в вызове await() .

  1. Откройте overview/OverviewViewModel.kt . В начале файла (после импорта, перед определением класса) добавьте enum для представления всех доступных статусов:
enum class MarsApiStatus { LOADING, ERROR, DONE }
  1. Переименуйте внутренние и внешние определения данных _response в классе OverviewViewModel в _status . Поскольку ранее в этой практической работе вы добавили поддержку _properties LiveData , полный ответ веб-сервиса остался неиспользованным. Здесь вам понадобится LiveData для отслеживания текущего состояния, поэтому вы можете просто переименовать существующие переменные.

Также измените типы со String на MarsApiStatus.

private val _status = MutableLiveData<MarsApiStatus>()

val status: LiveData<MarsApiStatus>
   get() = _status
  1. Прокрутите страницу вниз до метода getMarsRealEstateProperties() и измените здесь _response на _status . Измените строку "Success" на состояние MarsApiStatus.DONE , а строку "Failure" на MarsApiStatus.ERROR .
  2. Добавьте статус MarsApiStatus.LOADING в начало блока try {} перед вызовом await() . Это начальный статус, пока сопрограмма работает и вы ожидаете данные. Полный блок try/catch {} теперь выглядит так:
try {
    _status.value = MarsApiStatus.LOADING
   var listResult = getPropertiesDeferred.await()
   _status.value = MarsApiStatus.DONE
   _properties.value = listResult
} catch (e: Exception) {
   _status.value = MarsApiStatus.ERROR
}
  1. После появления ошибки в блоке catch {} установите _properties LiveData пустой список. Это очистит RecyclerView .
} catch (e: Exception) {
   _status.value = MarsApiStatus.ERROR
   _properties.value = ArrayList()
}

Шаг 2: Добавьте адаптер привязки для статуса ImageView

Теперь у вас есть статус в модели представления, но это всего лишь набор состояний. Как сделать так, чтобы он отображался в самом приложении? На этом этапе вы используете ImageView , подключенный к привязке данных, для отображения значков состояний загрузки и ошибки. Когда приложение находится в состоянии загрузки или ошибки, ImageView должен быть видимым. После завершения загрузки приложения ImageView должен быть невидимым.

  1. Откройте BindingAdapters.kt . Добавьте новый адаптер привязки с именем bindStatus() , который принимает в качестве аргументов значения ImageView и MarsApiStatus . Импортируйте com.example.android.marsrealestate.overview.MarsApiStatus по запросу.
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView, 
          status: MarsApiStatus?) {
}
  1. Добавьте when {} внутри метода bindStatus() для переключения между различными статусами.
when (status) {

}
  1. Внутри блока when {} добавьте кейс для состояния загрузки ( MarsApiStatus.LOADING ). Для этого состояния сделайте ImageView видимым и назначьте ему анимацию загрузки. Это тот же анимационный объект, который вы использовали для Glide в предыдущей задаче. Импортируйте android.view.View по запросу.
when (status) {
   MarsApiStatus.LOADING -> {
      statusImageView.visibility = View.VISIBLE
      statusImageView.setImageResource(R.drawable.loading_animation)
   }
}
  1. Добавьте случай для состояния ошибки — MarsApiStatus.ERROR . Аналогично тому, как вы сделали для состояния LOADING , установите для ImageView статус visible и повторно используйте отрисовываемый объект connection-error.
MarsApiStatus.ERROR -> {
   statusImageView.visibility = View.VISIBLE
   statusImageView.setImageResource(R.drawable.ic_connection_error)
}
  1. Добавьте кейс для состояния «Готово» — MarsApiStatus.DONE . В данном случае ответ успешен, поэтому отключите видимость ImageView статуса, чтобы скрыть его.
MarsApiStatus.DONE -> {
   statusImageView.visibility = View.GONE
}

Шаг 3: Добавьте статус ImageView в макет

  1. Откройте res/layout/fragment_overview.xml . Под элементом RecyclerView , внутри ConstraintLayout , добавьте элемент ImageView показанный ниже.

    Этот ImageView имеет те же ограничения, что и RecyclerView . Однако ширина и высота определяются с помощью wrap_content для центрирования изображения, а не растягиванием его до полного заполнения представления. Также обратите внимание на атрибут app:marsApiStatus , который позволяет представлению вызывать ваш BindingAdapter при изменении свойства status в модели представления.
<ImageView
   android:id="@+id/status_image"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    app:layout_constraintBottom_toBottomOf="parent"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:marsApiStatus="@{viewModel.status}" />
  1. Включите режим «В самолете» на эмуляторе или устройстве, чтобы имитировать отсутствие сетевого подключения. Скомпилируйте и запустите приложение. Обратите внимание на изображение ошибки:

  1. Нажмите кнопку «Назад», чтобы закрыть приложение, и выключите режим полёта. Чтобы вернуться в приложение, используйте экран «Недавние». В зависимости от скорости вашего сетевого подключения, перед началом загрузки изображений может появиться очень короткий индикатор загрузки, когда приложение обращается к веб-сервису.

Проект Android Studio: MarsRealEstateGrid

  • Чтобы упростить процесс управления изображениями, используйте библиотеку Glide для загрузки, буферизации, декодирования и кэширования изображений в вашем приложении.
  • Для загрузки изображения из Интернета Glide нужны две вещи: URL-адрес изображения и объект ImageView , в который нужно поместить изображение. Чтобы указать эти параметры, используйте методы load() и into() в Glide.
  • Адаптеры привязки — это методы расширения, которые располагаются между представлением и связанными с ним данными. Адаптеры привязки обеспечивают настраиваемое поведение при изменении данных, например, для вызова Glide для загрузки изображения из URL-адреса в ImageView .
  • Адаптеры привязки — это методы расширения, аннотированные аннотацией @BindingAdapter .
  • Чтобы добавить параметры в запрос Glide, используйте метод apply() . Например, используйте apply() с placeholder() для указания отрисовки загрузки, а apply() с error() — для указания отрисовки ошибки.
  • Для создания сетки изображений используйте RecyclerView с GridLayoutManager .
  • Чтобы обновить список свойств при его изменении, используйте адаптер привязки между RecyclerView и макетом.

Курс Udacity:

Документация для разработчиков Android:

Другой:

В этом разделе перечислены возможные домашние задания для студентов, работающих над этой лабораторной работой в рамках курса, проводимого преподавателем. Преподаватель должен выполнить следующие действия:

  • При необходимости задавайте домашнее задание.
  • Объясните учащимся, как следует сдавать домашние задания.
  • Оцените домашние задания.

Преподаватели могут использовать эти предложения так часто или редко, как пожелают, и могут свободно задавать любые другие домашние задания, которые они сочтут подходящими.

Если вы работаете с этой лабораторной работой самостоятельно, можете использовать эти домашние задания для проверки своих знаний.

Ответьте на эти вопросы

Вопрос 1

Какой метод Glide вы используете для указания ImageView , который будет содержать загруженное изображение?

into()

with()

imageview()

apply()

Вопрос 2

Как указать изображение-заполнитель, которое будет отображаться при загрузке Glide?

▢ Используйте метод into() с отрисовываемым объектом.

▢ Используйте RequestOptions() и вызовите метод placeholder() с отрисовываемым объектом.

▢ Назначьте свойство Glide.placeholder рисуемому объекту.

▢ Используйте RequestOptions() и вызовите метод loadingImage() с отрисовываемым изображением.

Вопрос 3

Как указать, что метод является адаптером привязки?

▢ Вызовите метод setBindingAdapter() в LiveData .

▢ Поместите метод в файл Kotlin с именем BindingAdapters.kt .

▢ Используйте атрибут android:adapter в макете XML.

▢ Добавьте аннотацию к методу с помощью @BindingAdapter .

Начните следующий урок: 8.3 Фильтрация и подробные просмотры с использованием интернет-данных

Ссылки на другие практические занятия по этому курсу см. на целевой странице практических занятий по основам Android Kotlin .