Saturday, March 7, 2020 / Android, Jetpack

Android Jetpack: RecyclerView を使った一番簡単なリスト

RecyclerView

いまさらですが Jetpack によるUI構築編、その1。 シンプルなリストを RecyclerView を使ってつくります。
これ Create a List with RecyclerView を参考にしました。

MainActivity クラス

RecyclerView の構築は、以下の2点がポイントです。

  • (1) レイアウトマネージャでタイプ(リスト, グリッド など)を指定
  • (2) リストの内容管理はアダプタで指定

コードではこんな感じ:

recyclerView.apply {
	layoutManager = viewManager // (1)
	adapter = viewAdapter // (2)
}

今回は 単純なリストにしたいので、 (1) layoutManager は LinearLayoutManager を使用します。

private val viewManager: RecyclerView.LayoutManager by lazy {
    LinearLayoutManager(this)
}

(2)の内容は文字列のリストを保持したアダプタを準備します。 データセットはサンプルとして 0から100までのリストアイテム用の文字列を生成しています。(3)

private val dataset: List<String> by lazy {
    val list = mutableListOf<String>()

    // (3)
    0.until(100).forEach {
        list.add("- item ${it}") 
    }
    list
}

private val viewAdapter: RecyclerView.Adapter<*> by lazy {
    MyAdapter(dataset)
}

以上をまとめた MainActivity.kt は以下のようになります。

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.*

class MainActivity : AppCompatActivity() {
    private val dataset: List<String> by lazy {
        val list = mutableListOf<String>()
        0.until(100).forEach {
            list.add("- item ${it}")
        }
        list
    }

    private val viewAdapter: RecyclerView.Adapter<*> by lazy {
        MyAdapter(dataset)
    }

    private val viewManager: RecyclerView.LayoutManager by lazy {
        LinearLayoutManager(this)
    }

    private val recyclerView: RecyclerView by lazy {
        findViewById<RecyclerView>(R.id.my_recycler_view)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main) // (4)

        recyclerView.apply {
            layoutManager = viewManager
            adapter = viewAdapter
        }
    }
}

(4) の部分で レイアウトを activity_main から生成していますが、これは app/src/main/res/layout/activity_main.xml で用意しておいた以下の内容です:

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout 
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#FFFFFF">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/my_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#FFFFFF" />

</FrameLayout>

RecyclerView を使うので、app/build.gradle の dependencies に implementation 'androidx.recyclerview:recyclerview:1.1.0' を追記する必要があります。(5)

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
    implementation 'androidx.appcompat:appcompat:1.1.0'
    implementation 'androidx.core:core-ktx:1.2.0'
    implementation 'androidx.recyclerview:recyclerview:1.1.0' // (5)
}

MyAdapter クラス

RecyclerView.Adapter のサブクラスとして MyAdapter クラスを実装します。 MyAdapterクラスではリストの各アイテムのViewと内容を結びつけるコードを書きます。

  • onCreateViewHolder で ViewHolder を生成して返す(これは各アイテムのViewを内包したViewHolderクラス)
  • onBindViewHolder で、ViewHolderと内容を結びつける
  • getItemCount で、リストアイテムの総数を返す

つまり、各アイテムを表示する部分はViewをそのまま使うのではなく、ViewHolderでラップする必要があるということですね。 そのViewHolderクラスの実装はこれ:

class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val textView = itemView.findViewById<TextView>(R.id.my_item) // (6)
}

そして、このMyViewHolderクラスを生成するのは onCreateViewHolder メソッド:

override fun onCreateViewHolder(
    parent: ViewGroup,
    viewType: Int): MyAdapter.MyViewHolder {

    val view: View = LayoutInflater.from(parent.context).inflate(R.layout.my_item_view, parent, false) // (7)
    return MyViewHolder(view)
}

R.id.my_item (6) や R.layout.my_item_view (7) などは、app/src/main/res/layout/my_item_view.xml に用意した以下の内容:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content">
    <TextView
        android:id="@+id/my_item"
        android:layout_height="wrap_content"
        android:layout_width="wrap_content"
        android:text="" />
</LinearLayout>

これらを全部まとめた MyAdapter.kt は:

import android.view.LayoutInflater
import android.view.*
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView

class MyAdapter(private val dataset: List<String>): RecyclerView.Adapter<MyAdapter.MyViewHolder>() {
    class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val textView = itemView.findViewById<TextView>(R.id.my_item)
    }

    override fun onCreateViewHolder(
        parent: ViewGroup,
        viewType: Int): MyAdapter.MyViewHolder {

        val view: View = LayoutInflater.from(parent.context).inflate(R.layout.my_item_view, parent, false)
        return MyViewHolder(view)
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        holder.textView.text = dataset[position]
    }

    override fun getItemCount() = dataset.size
}

まとめ

レイアウト関係をxmlファイルに書くので、コード側が簡潔になるのはうれしいにはうれしいですね。 コードだけみてもわからないのが嫌といえば嫌ですが。 単なる固定の文字列リストを出すだけならとても簡単につくることができました。 ありがとう Jetpack。 次回は、リストアイテムの内容を変更できるように改造してみようと思います。