この Codelab は、Android Kotlin の基礎コースの一部です。Codelab を順番に進めていくと、このコースを最大限に活用できます。すべてのコース Codelab は Android Kotlin の基礎 Codelab ランディング ページに掲載されています。
はじめに
前の Codelab では、ウェブサービスからデータを取得し、レスポンスを解析しデータ オブジェクトにする方法を学びました。この Codelab では、その知識に基づいて、ウェブ URL から写真を読み込んで表示します。また、RecyclerView
を作成して概要ページに画像のグリッドを表示する方法もおさらいします。
前提となる知識
- フラグメントの作成方法と使用方法。
- ビューモデル、ビューモデル ファクトリ、変換、
LiveData
などのアーキテクチャ コンポーネントの使用方法。 - REST ウェブサービスから JSON を取得し、Retrofit ライブラリと Moshi ライブラリを使用してそのデータを解析し、Kotlin オブジェクトに変換する方法。
RecyclerView
を使用してグリッド レイアウトを作成する方法。Adapter
、ViewHolder
、DiffUtil
の機能。
ラボの内容
- Glide ライブラリを使用して、ウェブ URL から画像を読み込んで表示する方法。
RecyclerView
とグリッド アダプターを使用して画像のグリッドを表示する方法。- 画像をダウンロードして表示する際に発生するエラーの処理方法。
演習内容
- MarsRealEstate アプリを変更して、Mars プロパティ データから画像 URL を取得し、Glide を使用してその画像を読み込んで表示します。
- 読み込み中のアニメーションとエラーアイコンをアプリに追加します。
RecyclerView
を使用して火星の物件画像のグリッドを表示します。- ステータス処理とエラー処理を
RecyclerView
に追加します。
この Codelab(および関連する Codelab)では、MarsRealEstate という名前のアプリを使用します。このアプリには火星での販売物件が表示されます。アプリはインターネット サーバーに接続して、価格や宿泊施設が販売または賃貸可能かどうかなど、宿泊施設のデータを取得して表示します。それぞれのは、NASA の火星探査機が撮影した火星の実際の写真です。
この Codelab で作成するアプリのバージョンは概要ページに示されます。概要ページには、画像がグリッドで表示されます。これらの画像は、アプリが火星不動産ウェブサービスから取得した宿泊施設データの一部です。アプリは Glide ライブラリを使って画像の読み込みと表示を行い、RecyclerView
を使用して画像のグリッド レイアウトを作成します。また、アプリはネットワーク エラーを適切に処理します。
ウェブ URL からの写真を表示するのは簡単に思えますが、適切に処理するにはかなりの作業が必要です。画像をダウンロードして圧縮し、圧縮形式から Android が使用できる形式にデコードする必要があります。メモリ内キャッシュとストレージベースのキャッシュの両方またはいずれかに画像を保存する必要があります。これらすべてを優先度の低いバックグラウンド スレッドで行いつつ、UI の応答性を維持する必要があります。また、ネットワークと CPU のパフォーマンスを最適化するため、複数の画像を一度に取得してデコードする必要もあります。ネットワークから画像を効果的に読み込む方法は、それ自体が Codelab です。
幸いなことに、コミュニティで開発された Glide というライブラリを使用して、画像のダウンロード、バッファリング、デコード、キャッシュ保存を行うことができます。Glide を使用すると、このような処理を最初からすべて行う場合よりもはるかに手間が省けます。
Glide には、基本的に次の 2 つのものが必要です。
- 読み込んで表示する画像の URL。
- 画像を表示する
ImageView
オブジェクト。
このタスクでは、Glide を使用して、不動産ウェブサービスの画像を 1 つだけ表示する方法を説明します。ウェブサービスから返されたプロパティのリストで、最初の火星プロパティを表す画像を表示します。画像の表示前と表示後のスクリーンショットを次に示します。
ステップ 1: Glide の依存関係を追加する
- 前回の Codelab で作成した MarsRealEstate アプリを開きます。(アプリがインストールされていない場合は、MarsRealEstateNetwork をダウンロードできます)。
- アプリを実行して動作を確認します(たとえば、火星で架空の架空の物件について詳細を確認できます)。
- build.gradle(モジュール: app)を開きます。
dependencies
セクションに、Glide ライブラリの次の行を追加します。
implementation "com.github.bumptech.glide:glide:$version_glide"
バージョン番号は、プロジェクトの Gradle ファイルですでに定義されています。
- [Sync Now] をクリックして、新しい依存関係でプロジェクトを再ビルドします。
ステップ 2: ビューモデルを更新する
次に、OverviewViewModel
クラスを更新して、単一の火星プロパティのライブデータを追加します。
overview/OverviewViewModel.kt
を開きます。_response
のLiveData
のすぐ下で、単一のMarsProperty
オブジェクトに対して内部(可変)ライブデータと外部(不変)ライブデータの両方を追加します。
リクエストされたら、MarsProperty
クラス(com.example.android.marsrealestate.network.MarsProperty
)をインポートします。
private val _property = MutableLiveData<MarsProperty>()
val property: LiveData<MarsProperty>
get() = _property
getMarsRealEstateProperties()
メソッドで、_response.value
をプロパティ数に設定するtry/catch {}
ブロック内の行を見つけます。下記のテストを追加します。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}"
}
res/layout/fragment_overview.xml
ファイルを開きます。<TextView>
要素で、android:text
を変更してproperty
LiveData
のimgSrcUrl
コンポーネントにバインドします。
android:text="@{viewModel.property.imgSrcUrl}"
- アプリを実行します。
TextView
は最初の火星プロパティにある画像の URL のみを表示します。ここまでの作業では、ビューモデルと、その URL のライブデータを設定します。
ステップ 3: バインディング アダプターを作成して Glide を呼び出す
表示する画像の URL を取得したら、Glide でその画像の読み込みを開始します。このステップでは、バインディング アダプターを使用して、ImageView
に関連付けられた XML 属性から URL を取得し、Glide を使用して画像を読み込みます。バインディング アダプターは、ビューとバインドされたデータの間に位置する拡張メソッドで、データが変更されたときにカスタム動作を提供します。この場合のカスタム動作は、Glide を呼び出して、URL から ImageView
に画像を読み込むことです。
BindingAdapters.kt
を開きます。このファイルは、アプリ全体で使用するバインディング アダプターを保持します。ImageView
とString
をパラメータとして受け取るbindImage()
関数を作成します。関数に@BindingAdapter
アノテーションを付けます。@BindingAdapter
アノテーションは、XML 項目にimageUrl
属性がある場合にこのバインディング アダプターを実行することをデータ バインディングに指示します。
リクエストされたら、androidx.databinding.BindingAdapter
とandroid.widget.ImageView
をインポートします。
@BindingAdapter("imageUrl")
fun bindImage(imgView: ImageView, imgUrl: String?) {
}
bindImage()
関数内で、imgUrl
引数にlet {}
ブロックを追加します。
imgUrl?.let {
}
let {}
ブロック内に、下記の行を追加して、URL 文字列を(XML から)Uri
オブジェクトに変換します。リクエストされたら、androidx.core.net.toUri
をインポートします。
イメージを pull するサーバーにはこのスキームが必要なため、最後のUri
オブジェクトに HTTPS スキームを使用します。HTTPS スキームを使用するには、buildUpon.scheme("https")
をtoUri
ビルダーの末尾に追加します。toUri()
メソッドは、Android KTX コアライブラリの Kotlin 拡張関数であるため、String
クラスの一部に見えます。
val imgUri = imgUrl.toUri().buildUpon().scheme("https").build()
- 引き続き
let {}
内で、Glide.with()
を呼び出して、Uri
オブジェクトからImageView
に画像を読み込みます。リクエストされたら、com.bumptech.glide.Glide
をインポートします。
Glide.with(imgView.context)
.load(imgUri)
.into(imgView)
ステップ 4: レイアウトとフラグメントを更新する
Glide は画像を読み込みましたが、まだ確認できていません。次のステップでは、ImageView
を使用してレイアウトとフラグメントを更新し、画像を表示します。
res/layout/gridview_item.xml
を開きます。これは、この Codelab で後述するRecyclerView
の項目ごとに使用するレイアウト リソース ファイルです。ここで一時的に使用して、1 つの画像のみを表示します。<ImageView>
要素の上にデータ バインディング用の<data>
要素を追加して、OverviewViewModel
クラスにバインドします。
<data>
<variable
name="viewModel"
type="com.example.android.marsrealestate.overview.OverviewViewModel" />
</data>
- 新しい画像読み込みバインディング アダプターを使用するには、
ImageView
要素にapp:imageUrl
属性を追加します。
app:imageUrl="@{viewModel.property.imgSrcUrl}"
overview/OverviewFragment.kt
を開きます。onCreateView()
メソッドで、FragmentOverviewBinding
クラスをインフレートする行をコメントアウトし、バインディング変数に割り当てます。これは一時的なものであり、後で戻ります。
//val binding = FragmentOverviewBinding.inflate(inflater)
- 代わりに、
GridViewItemBinding
クラスをインフレートする行を追加します。リクエストされたら、com.example.android.marsrealestate. databinding.GridViewItemBinding
をインポートします。
val binding = GridViewItemBinding.inflate(inflater)
- アプリを実行します。結果リストの最初の
MarsProperty
の画像が表示されます。
ステップ 5: シンプルな読み込み画像とエラー画像を追加する
Glide では、画像の読み込み中にプレースホルダ画像を表示し、読み込みが失敗した場合(画像がない、画像が破損しているなど)にエラー画像を表示することで、ユーザー エクスペリエンスを改善できます。このステップでは、バインディング アダプターとレイアウトにこの機能を追加します。
res/drawable/ic_broken_image.xml
を開いて、右側の [プレビュー] タブをクリックします。エラー画像としては、組み込みのアイコン ライブラリにある破損した画像のアイコンを使用します。このベクター型ドローアブルでは、android:tint
属性によりアイコンがグレーに色付けされています。
res/drawable/loading_animation.xml
を開きます。このドローアブルは、<animate-rotate>
タグで定義されるアニメーションです。アニメーションにより、中心点の周りで画像ドローアブルloading_img.xml
が回転します。(プレビューにはアニメーションは表示されません)。
BindingAdapters.kt
ファイルに戻ります。bindImage()
メソッドで、Glide.with()
の呼び出しを更新して、load()
とinto()
の間でapply()
関数を呼び出します。リクエストされたら、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)
}
}
- アプリを実行します。ネットワーク接続の速度によっては、Glide がプロパティ画像をダウンロードして表示する間、一瞬だけ読み込み中の画像が表示されます。しかし、ネットワークを切断しても、破損した画像のアイコンは表示されません。この問題は、Codelab の最後の部分で修正します。
アプリがインターネットから宿泊施設の情報を読み込むようになりました。最初の MarsProperty
リスト項目のデータを使って、ビューモデルに LiveData
プロパティを作成し、そのプロパティ データの画像 URL を使って ImageView
に入力しました。しかし、目標はアプリで画像のグリッドを表示することです。したがって、GridLayoutManager
で RecyclerView
を使用します。
ステップ 1: ビューモデルを更新する
現在、ビューモデルには、1 つの MarsProperty
オブジェクト(ウェブサービスからのレスポンス リストの最初のオブジェクト)を保持する _property
LiveData
があります。このステップでは、LiveData
を変更して、MarsProperty
オブジェクトのリスト全体を保持するようにします。
overview/OverviewViewModel.kt
を開きます。- プライベートの
_property
変数を_properties
に変更します。型をMarsProperty
オブジェクトのリストに変更します。
private val _properties = MutableLiveData<List<MarsProperty>>()
- 外部
property
ライブデータをproperties
に置き換えます。同様に、ここでリストをLiveData
タイプに追加します。
val properties: LiveData<List<MarsProperty>>
get() = _properties
- 下にスクロールして
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: レイアウトとフラグメントを更新する
次のステップとして、単一の画像ビューではなく、リサイクラー ビューとグリッド レイアウトを使用するように、アプリのレイアウトとフラグメントを変更します。
res/layout/gridview_item.xml
を開きます。データ バインディングをOverviewViewModel
からMarsProperty
に変更し、変数の名前を"property"
に変更します。
<variable
name="property"
type="com.example.android.marsrealestate.network.MarsProperty" />
<ImageView>
で、MarsProperty
オブジェクト内の画像 URL を参照するようにapp:imageUrl
属性を変更します。
app:imageUrl="@{property.imgSrcUrl}"
overview/OverviewFragment.kt
を開きます。onCreateview()
で、FragmentOverviewBinding
をインフレートする行のコメント化を解除します。GridViewBinding
をインフレートする行を削除するか、コメントアウトします。上記の変更により、前のタスクで行った一時的な変更が取り消されます。
val binding = FragmentOverviewBinding.inflate(inflater)
// val binding = GridViewItemBinding.inflate(inflater)
res/layout/fragment_overview.xml
を開きます。<TextView>
要素全体を削除します。- 代わりに、以下の
<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
レイアウトに 1 つの ImageView
が追加されました。このステップでは、RecyclerView
アダプターを使用してデータを RecyclerView
にバインドします。
overview/PhotoGridAdapter.kt
を開きます。- 下記のコンストラクタ パラメータを使って、
PhotoGridAdapter
クラスを作成します。PhotoGridAdapter
クラスはListAdapter
を拡張します。このクラスのコンストラクタには、リストアイテムの型、ビューホルダー、DiffUtil.ItemCallback
実装が必要です。
リクエストされたら、androidx.recyclerview.widget.ListAdapter
クラスとcom.example.android.marsrealestate.network.MarsProperty
クラスをインポートします。次のステップでは、このコンストラクタに欠けている、エラーを生成する他の部分を実装します。
class PhotoGridAdapter : ListAdapter<MarsProperty,
PhotoGridAdapter.MarsPropertyViewHolder>(DiffCallback) {
}
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")
}
- 次のように、追加したクラスの後に
PhotoGridAdapter
クラス定義の最後にDiffCallback
のコンパニオン オブジェクト定義を追加します。
リクエストされたら、androidx.recyclerview.widget.DiffUtil
をインポートします。DiffCallback
オブジェクトは、比較するオブジェクトのタイプ(MarsProperty
)でDiffUtil.ItemCallback
を拡張します。
companion object DiffCallback : DiffUtil.ItemCallback<MarsProperty>() {
}
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") }
areItemsTheSame()
メソッドの TODO を削除します。Kotlin の参照等価演算子(===
)を使用します。これは、oldItem
とnewItem
のオブジェクト参照が同じ場合にtrue
を返します。
override fun areItemsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem === newItem
}
areContentsTheSame()
では、oldItem
とnewItem
の ID にのみ標準の等価演算子を使用します。
override fun areContentsTheSame(oldItem: MarsProperty,
newItem: MarsProperty): Boolean {
return oldItem.id == newItem.id
}
- 引き続き
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) {
}
MarsPropertyViewHolder
内で、MarsProperty
オブジェクトを引数として受け取り、binding.property
をそのオブジェクトに設定するbind()
メソッドを作成します。プロパティを設定した後、executePendingBindings()
を呼び出します。これにより、更新が直ちに実行されます。
fun bind(marsProperty: MarsProperty) {
binding.property = marsProperty
binding.executePendingBindings()
}
onCreateViewHolder()
で、TODO を削除して下記の行を追加します。リクエストされたら、android.view.LayoutInflater
をインポートします。onCreateViewHolder()
メソッドは、GridViewItemBinding
をインフレートし、親ViewGroup
コンテキストのLayoutInflater
を使用して作成された、新しいMarsPropertyViewHolder
を返す必要があります。
return MarsPropertyViewHolder(GridViewItemBinding.inflate(
LayoutInflater.from(parent.context)))
onBindViewHolder()
メソッドで、TODO を削除して下記の行を追加します。ここでgetItem()
を呼び出して、現在のRecyclerView
の位置に関連付けられているMarsProperty
オブジェクトを取得し、そのプロパティをMarsPropertyViewHolder
のbind()
メソッドに渡します。
val marsProperty = getItem(position)
holder.bind(marsProperty)
ステップ 4: バインディング アダプターを追加して要素を接続する
最後に、BindingAdapter
を使用して、MarsProperty
オブジェクトのリストで PhotoGridAdapter
を初期化します。BindingAdapter
を使用して RecyclerView
のデータを設定すると、LiveData
に MarsProperty
オブジェクトのリストがあるかどうかがデータ バインディングによって自動的に監視されるようになります。MarsProperty
リストが変更されると、バインディング アダプターが自動的に呼び出されます。
BindingAdapters.kt
を開きます。- ファイルの末尾に、
RecyclerView
とMarsProperty
オブジェクトのリストを引数として受け取るbindRecyclerView()
メソッドを追加します。そのメソッドに@BindingAdapter
アノテーションを付けます。
リクエストされたら、androidx.recyclerview.widget.RecyclerView
とcom.example.android.marsrealestate.network.MarsProperty
をインポートする。
@BindingAdapter("listData")
fun bindRecyclerView(recyclerView: RecyclerView,
data: List<MarsProperty>?) {
}
bindRecyclerView()
関数内で、recyclerView.adapter
をPhotoGridAdapter
にキャストし、そのデータを使用してadapter.submitList()
を呼び出します。これにより、新しいリストが利用可能であることがRecyclerView
に通知されます。
リクエストされたら、com.example.android.marsrealestate.overview.PhotoGridAdapter
をインポートします。
val adapter = recyclerView.adapter as PhotoGridAdapter
adapter.submitList(data)
res/layout/fragment_overview.xml
を開きます。app:listData
属性をRecyclerView
要素に追加し、データ バインディングを使用してviewmodel.properties
に設定します。
app:listData="@{viewModel.properties}"
overview/OverviewFragment.kt
を開きます。onCreateView()
で、setHasOptionsMenu()
を呼び出す直前に、binding.photosGrid
のRecyclerView
アダプターを新しいPhotoGridAdapter
オブジェクトに初期化します。
binding.photosGrid.adapter = PhotoGridAdapter()
- アプリを実行します。
MarsProperty
の画像がグリッドで表示されます。スクロールすると新しい画像が表示され、画像自体が表示される前に読み込みの進行状況アイコンが表示されます。機内モードをオンにすると、まだ読み込まれていない画像が破損した画像のアイコンとして表示されます。
MarsRealEstate アプリは、画像を取得できない場合、破損した画像のアイコンを表示します。ネットワークがない場合は、空白の画面を表示します。
これはユーザー エクスペリエンスとしては不適切です。このタスクでは、基本的なエラー処理を追加して、何が起こっているかをユーザーが理解できるようにします。インターネットが利用できない場合、接続エラーアイコンが表示されます。アプリが MarsProperty
リストを取得している間、読み込み中のアニメーションが表示されます。
ステップ 1: ビューモデルにステータスを追加する
まず、ビューモデルで、ウェブ リクエストのステータスを表す LiveData
を作成します。考慮する必要があるステータスは、読み込み中、成功、失敗の 3 つです。読み込み状態は、await()
の呼び出し内でデータを待っているときに発生します。
overview/OverviewViewModel.kt
を開きます。ファイルの先頭部分(インポートの後、クラス定義の前)に、すべての使用可能なステータスを表すenum
を追加します。
enum class MarsApiStatus { LOADING, ERROR, DONE }
OverviewViewModel
クラス全体の内部と外部の_response
ライブデータ定義の名前を_status
に変更しました。この Codelab ではすでに_properties
LiveData
のサポートを追加したため、完全なウェブサービス レスポンスは使用されていません。現在の変数名を変更するには、ここにLiveData
が必要です。
また、型を String
から MarsApiStatus.
に変更します。
private val _status = MutableLiveData<MarsApiStatus>()
val status: LiveData<MarsApiStatus>
get() = _status
getMarsRealEstateProperties()
メソッドまで下にスクロールして、ここでも_response
を_status
に更新します。"Success"
文字列をMarsApiStatus.DONE
状態に変更し、"Failure"
文字列をMarsApiStatus.ERROR
に変更します。try {}
ブロックの先頭に、await()
を呼び出す前にMarsApiStatus.LOADING
ステータスを追加します。これは、コルーチンを実行してデータを待機しているときの初期ステータスです。完全な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
}
catch {}
ブロックのエラー状態の後で、_properties
LiveData
を空のリストに設定します。これにより、RecyclerView
がクリアされます。
} catch (e: Exception) {
_status.value = MarsApiStatus.ERROR
_properties.value = ArrayList()
}
ステップ 2: ステータス ImageView のバインディング アダプターを追加する
これで、ビューモデルにはステータスがありますが、ステータスのセットだけになります。アプリ内でどのように表示するかこのステップでは、データ バインディングに接続された ImageView
を使用して、読み込み状態とエラー状態のアイコンを表示します。アプリが読み込み中またはエラーの状態にあるときは、ImageView
を表示する必要があります。アプリが読み込みを完了したときは、ImageView
を非表示にする必要があります。
BindingAdapters.kt
を開きます。ImageView
とMarsApiStatus
値を引数として受け取るbindStatus()
という名前の新しいバインディング アダプターを追加します。リクエストされたら、com.example.android.marsrealestate.overview.MarsApiStatus
をインポートします。
@BindingAdapter("marsApiStatus")
fun bindStatus(statusImageView: ImageView,
status: MarsApiStatus?) {
}
- ステータスに応じて処理を切り替えるため、
bindStatus()
メソッド内にwhen {}
を追加します。
when (status) {
}
when {}
内に、「読み込み中」状態(MarsApiStatus.LOADING
)の処理を追加します。この状態の場合は、ImageView
を「表示」に設定し、読み込み中のアニメーションを割り当てます。これは、前のタスクで Glide に使用したアニメーション ドローアブルと同じです。リクエストされたら、android.view.View
をインポートします。
when (status) {
MarsApiStatus.LOADING -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.loading_animation)
}
}
- エラー状態(
MarsApiStatus.ERROR
)の処理を追加します。LOADING
状態の処理と同様に、ステータスImageView
を「表示」に設定し、接続エラー ドローアブルを再利用します。
MarsApiStatus.ERROR -> {
statusImageView.visibility = View.VISIBLE
statusImageView.setImageResource(R.drawable.ic_connection_error)
}
- 完了状態(
MarsApiStatus.DONE
)の処理を追加します。この場合は成功レスポンスが返されるので、ステータスImageView
の表示をオフにして非表示にします。
MarsApiStatus.DONE -> {
statusImageView.visibility = View.GONE
}
ステップ 3: ステータス ImageView をレイアウトに追加する
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}" />
- エミュレータまたはデバイスで機内モードをオンにして、ネットワーク接続の欠落をシミュレートします。アプリをコンパイルして実行します。エラー画像が表示されることを確認してください。
- 戻るボタンをタップしてアプリを閉じ、機内モードをオフにします。最近使ったアプリの画面を使用してアプリに戻ります。ネットワーク接続の速度によっては、画像の読み込みを開始する前にアプリがウェブサービスをクエリしている間、一瞬だけ読み込み中のアイコンが表示されることがあります。
Android Studio プロジェクト: MarsRealEstateGrid
- 画像の管理プロセスを簡略化するには、Glide ライブラリを使用して、アプリで画像をダウンロード、バッファリング、デコード、キャッシュに保存します。
- Glide は、インターネットから画像を読み込むために、画像の URL と、画像を配置するための
ImageView
オブジェクトの 2 つを必要とします。これらのオプションを指定するには、Glide でload()
メソッドとinto()
メソッドを使用します。 - バインディング アダプターは、ビューとビューのバインドされたデータをつなげる拡張メソッドです。バインディング アダプターは、データが変更されたときのカスタム動作を提供します。たとえば、Glide を呼び出して URL から
ImageView
に画像を読み込むことができます。 - バインディング アダプターは、
@BindingAdapter
アノテーション付きの拡張メソッドです。 - Glide リクエストにオプションを追加するには、
apply()
メソッドを使用します。たとえば、読み込みドローアブルを指定するにはapply()
とplaceholder()
を使用し、エラー ドローアブルを指定するにはapply()
とerror()
を使用します。 - 画像のグリッドを作成するには、
RecyclerView
とGridLayoutManager
を使用します。 - プロパティが変更されたときにプロパティのリストを更新するには、
RecyclerView
とレイアウトの間にバインディング アダプターを使用します。
Udacity コース:
Android デベロッパー ドキュメント:
その他:
このセクションでは、インストラクターが主導するコースの一環として、この Codelab に取り組む生徒の課題について説明します。教師は以下のことを行えます。
- 必要に応じて課題を割り当てます。
- 宿題の提出方法を生徒に伝える。
- 宿題を採点します。
教師はこれらの提案を少しだけ使うことができます。また、他の課題は自由に割り当ててください。
この Codelab にご自分で取り組む場合は、これらの課題を使用して知識をテストしてください。
次の質問に答えてください
問題 1
読み込まれた画像を含む ImageView
を示すには、どの Glide メソッドを使用しますか。
▢ into()
▢ with()
▢ imageview()
▢ apply()
質問 2
Glide の読み込み時に表示するプレースホルダ画像を指定するには、どうすればよいですか。
▢ ドローアブルで into()
メソッドを使用します。
▢ RequestOptions()
を使用し、ドローアブルで placeholder()
メソッドを呼び出します。
▢ Glide.placeholder
プロパティをドローアブルに割り当てる
▢ RequestOptions()
を使用し、ドローアブルで loadingImage()
メソッドを呼び出します。
問題 3
メソッドがバインディング アダプターであることを示すには、どうすればよいですか。
▢ LiveData
に対して setBindingAdapter()
メソッドを呼び出します。
▢ メソッドを、BindingAdapters.kt
という Kotlin ファイルに追加します。
▢ XML レイアウト内で android:adapter
属性を使ってください。
▢ メソッドに @BindingAdapter
アノテーションを付けます。
次のレッスンを開始する:
このコースの他の Codelab へのリンクについては、Android Kotlin の基礎 Codelab ランディング ページをご覧ください。