Android Kotlin基礎講座 07.1:RecyclerViewの基礎

タスク:全データ用のViewHolderを作成する

このタスクでは、シンプルだったビューホルダーをよりたくさんのdataを表示できるものに置き換えていきます。

Util.ktに追加したシンプルなViewHolderはTextItemViewHolderのTextViewをラップしているだけです。

class TextItemViewHolder(val textView: TextView): RecyclerView.ViewHolder(textView)

ではRecyclerViewが直接TextViewを使ってもいいのではないかと考える方もいるかと思います。実はこの一行のコードは多くの機能を提供してくれています。ViewHolderはアイテムビューとそのRecyclerViewでの位置に関するメタデータを説明しています。RecyclerViewはリストがスクロールしたときにビューを正しく配置するため、そしてAdapter内でアイテムが追加されたり削除されたりしたときのビューのアニメーションを付けるなどといったことを行うためにこの機能に頼っています。

RecyclerViewがViewHolder内に保存されているビューにアクセスする必要がある場合は、ビューホルダーのitemViewプロパティを使うことでアクセスすることができます。RecyclerViewは画面上に表示するためにアイテムをバインディングするとき、境界線のようなビュー周りに装飾を描写するときやアクセシビリティを実装するときにitemViewを使用します。

ステップ1:アイテムレイアウトを作成する

このステップでは、アイテム用のレイアウトを作成します。レイアウトは睡眠の質を表すImageViewが含まれているConstraintLayout、睡眠時間用のTextView、質を文字で表すためのTextViewから構成されます。レイアウトは既に作られたものがあるので、XMLコードをコピー&ペーストしていきます。

  1. 新しいレイアウトファイルを作成し、list_item_sleep_nightと名付けてください。
  2. 全てのコードを以下のコードで置き換えてください。作成したレイアウトに一通り目を通して確認しておいてください。
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:layout_width="match_parent"
   android:layout_height="wrap_content">

   <ImageView
       android:id="@+id/quality_image"
       android:layout_width="@dimen/icon_size"
       android:layout_height="60dp"
       android:layout_marginStart="16dp"
       android:layout_marginTop="8dp"
       android:layout_marginBottom="8dp"
       app:layout_constraintBottom_toBottomOf="parent"
       app:layout_constraintStart_toStartOf="parent"
       app:layout_constraintTop_toTopOf="parent"
       tools:srcCompat="@drawable/ic_sleep_5" />

   <TextView
       android:id="@+id/sleep_length"
       android:layout_width="0dp"
       android:layout_height="20dp"
       android:layout_marginStart="8dp"
       android:layout_marginTop="8dp"
       android:layout_marginEnd="16dp"
       app:layout_constraintEnd_toEndOf="parent"
       app:layout_constraintStart_toEndOf="@+id/quality_image"
       app:layout_constraintTop_toTopOf="@+id/quality_image"
       tools:text="Wednesday" />

   <TextView
       android:id="@+id/quality_string"
       android:layout_width="0dp"
       android:layout_height="20dp"
       android:layout_marginTop="8dp"
       app:layout_constraintEnd_toEndOf="@+id/sleep_length"
       app:layout_constraintHorizontal_bias="0.0"
       app:layout_constraintStart_toStartOf="@+id/sleep_length"
       app:layout_constraintTop_toBottomOf="@+id/sleep_length"
       tools:text="Excellent!!!" />
</androidx.constraintlayout.widget.ConstraintLayout>
  1. デザインタブに切り替えてください。デザインビューでは、レイアウトは以下のスクリーンショットの左のように見えます。ブループリントビューでは、右のように表示されます。
8240174f46c2c380.png

ステップ2:ViewHolderを作成する

  1. SleepNightAdapter.ktを開いてください。
  2. SleepNightAdapterの中にViewHolderというクラスを作成し、RecyclerView.ViewHolderを継承してください。
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView){}
  1. ViewHolderの中で、ビューの参照を取得します。このViewHolderが更新するビューの参照が必要です。このViewHolderをバインドする度に、画像と二つのテキストビューにアクセスする必要があります。(後にデータバインディングを使うためにこのコードを変換します)
val sleepLength: TextView = itemView.findViewById(R.id.sleep_length)
val quality: TextView = itemView.findViewById(R.id.quality_string)
val qualityImage: ImageView = itemView.findViewById(R.id.quality_image)

ステップ3:SleepNightAdapterのViewHolderを使う

  1. SleepNightAdapterの定義中で、TextItemViewHolderの代わりに先ほど作成したSleepNightAdapter.ViewHolderを使ってください。
class SleepNightAdapter: RecyclerView.Adapter<SleepNightAdapter.ViewHolder>() {

onCreateViewHolder()を更新します:

  1. onCreateViewHolder()のシグネチャをViewHolderを返すように変更してください。
  2. レイアウトインフレーターを正しいレイアウトリソースである、list_item_sleep_nightを使うように変更してください。
  3. TextViewのキャストを削除してください。
  4. TextItemViewHolderを返す代わりに、ViewHolderを返すようにしてください。

以下が変更後のonCreateViewHolder()関数です。

    override fun onCreateViewHolder(
            parent: ViewGroup, viewType: Int): ViewHolder {
        val layoutInflater = 
            LayoutInflater.from(parent.context)
        val view = layoutInflater
                .inflate(R.layout.list_item_sleep_night, 
                         parent, false)
        return ViewHolder(view)
    }

onBindViewHolder()を更新します:

  1. onBindViewHolder()をシグネチャを変更して、holderパラメーターがTextItemViewHolderではなくViewHolderであるようにしてください。
  2. onBindViewHolder()内のitemの宣言以外の全てのコードを削除してください。
  3. このビュー用のresourcesの参照を保存するためのval resを定義してください。
val res = holder.itemView.context.resources
  1. sleepLengthテキストビューのテキストに睡眠時間をセットしてください。スターターコードと共に提供されているフォーマットを行う関数を呼び出す以下のコードをコピーしてください。
holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
  1. convertDurationToFormatted()は定義される必要があるため、エラーが発生します。Util.ktを開き、コードをアンコメントし、関連するものをインポートしてください。(Code > Comment with Line commentsを選択してください)
  2. onBindViewHolder()に戻り、質をセットするために、convertNumericQualityToString()を使用してください。
holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
  1. 手動で以下の関数をインポートする必要があります。
import com.example.android.trackmysleepquality.convertDurationToFormatted
import com.example.android.trackmysleepquality.convertNumericQualityToString
  1. 質用の正しいアイコンをセットしてください。新しいic_sleep_activeアイコンはスターターコードの中に含まれています。
holder.qualityImage.setImageResource(when (item.sleepQuality) {
   0 -> R.drawable.ic_sleep_0
   1 -> R.drawable.ic_sleep_1
   2 -> R.drawable.ic_sleep_2
   3 -> R.drawable.ic_sleep_3
   4 -> R.drawable.ic_sleep_4
   5 -> R.drawable.ic_sleep_5
   else -> R.drawable.ic_sleep_active
})
  1. 以下が変更後のonBindViewHolder()関数になります。ViewHolder用の全てのデータをセットしています。
   override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val item = data[position]
        val res = holder.itemView.context.resources
        holder.sleepLength.text = convertDurationToFormatted(item.startTimeMilli, item.endTimeMilli, res)
        holder.quality.text= convertNumericQualityToString(item.sleepQuality, res)
        holder.qualityImage.setImageResource(when (item.sleepQuality) {
            0 -> R.drawable.ic_sleep_0
            1 -> R.drawable.ic_sleep_1
            2 -> R.drawable.ic_sleep_2
            3 -> R.drawable.ic_sleep_3
            4 -> R.drawable.ic_sleep_4
            5 -> R.drawable.ic_sleep_5
            else -> R.drawable.ic_sleep_active
        })
    }
  1. アプリを起動してください。画面は以下の画像のようになります。質用のアイコンと睡眠時間と睡眠の質を表すテキストが表示されています。
d5deef86fa39fbfc.png