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

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

Введение

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

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

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

Что вы узнаете

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

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

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

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

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

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

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

Glide в основном нужны две вещи:

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

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

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

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


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

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

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

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

  1. Откройте overview/OverviewViewModel.kt . Сразу под LiveData для _response добавьте как внутренние (изменяемые), так и внешние (неизменяемые) оперативные данные для одного объекта 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 и щелкните вкладку Preview справа. Для изображения ошибки вы используете значок сломанного изображения, доступный во встроенной библиотеке значков. Этот векторный рисунок использует атрибут 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. Обновите модель представления

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

  1. Откройте overview/OverviewViewModel.kt .
  2. Измените приватную переменную _property на _properties . Измените тип на список объектов MarsProperty .
private val _properties = MutableLiveData<List<MarsProperty>>()
  1. Замените оперативные данные внешнего property 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 по запросу.

    Вам нужна переменная GridViewItemBinding для привязки MarsProperty к макету, поэтому передайте переменную в 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 значение visible и назначьте ему анимацию загрузки. Это та же анимация, которую вы использовали для Glide в предыдущей задаче. Импортируйте android.view.View по запросу.
when (status) {
   MarsApiStatus.LOADING -> {
      statusImageView.visibility = View.VISIBLE
      statusImageView.setImageResource(R.drawable.loading_animation)
   }
}
  1. Добавьте случай для состояния ошибки MarsApiStatus.ERROR . Аналогично тому, что вы сделали для состояния LOADING , установите для состояния ImageView значение visible и повторно используйте отрисовку ошибки подключения.
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 при изменении свойства состояния в модели представления.
<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 и макетом.

Удасити курс:

Документация для разработчиков 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 Fundamentals .