2022年度版 RecyclerViewをわかりやすく解説(Kotlin, View Binding)

# はじめに
「リスト表示程度で難しすぎる！！」
全初心者がそう思うであろうRecyclerViewを超わかりやすく解説します。

::: note warn
本記事ではViewBindingの使用を前提に書いております。
findViewByIdより明らかに見やすく動作も早いです。
知らない人は調べてね！！
:::

↓完成図
<img width="300" alt="Screenshot_20220427-155940.jpg" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2643920/1a4efbce-c38d-2bc8-1ae7-6d6e1895cec1.jpeg">





# RecycleViewとは
見た目をカスタム可能なリスト表示です。
特徴として、例えば100個のアイテムを表示したいとき、スマートフォンの画面に一度に見えているのは高々10個だとしましょう。
そしたら残りの90個のレイアウトを用意しておくのって無駄ですよね。
だから見えてる10個のレイアウトを再利用して使いまわす(Recycle)からメモリを圧迫しないよっていう機能を備えています。
しかし、そのせいで簡単に使えなくなってます。いろいろAdapterやらをかませなければいけません。

# 必要なもの

- 1アイテム分のレイアウト
- RecyclerView.ViewHolderを継承したクラス
- RecyclerView.Adapterを継承したクラス

この3つを揃えてやっとActivityやFragmentでRecyclerViewを使えます。

## 1アイテム分のレイアウト
表示されるアイテム1行分のレイアウトファイルです。
res>layoutで右クリックしてNew>Layout Resource Fileを選びましょう。
File nameの項目は任意(今回は"list_item")、
Root elementも任意ですが、わからない方は簡単なLinearLayoutにしましょう。

今回は以下のような構成にしました。
```:list_item.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/itemText1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

    <TextView
        android:id="@+id/itemText2"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>
```
今回はテストのためにtextViewを2行表示するだけのシンプルなものです。

::: note warn
初期状態でルートの高さが"match_parent"になっていると思いますが、必ず固定長か"wrap_content"にしてください。
:::




## RecyclerView.ViewHolderを継承したクラス
こいつは使いまわすためのレイアウトを保存しておくホルダーです。
使っていくうちにどう機能しているかわかると思います。
このクラスの置き場所なのですが、独立したファイルにするのではなく、Adapterを継承したクラスの内部クラスにするのが一般的なようです。
なので今は説明だけ聞いて新規ファイル作成しないでください。

```:MyItemAdapter.ktの中に入れる予定
class MyViewHolder(val binding: ListItemBinding): RecyclerView.ViewHolder(binding.root)
```
ViewBindingを使用する場合、衝撃的なほどに簡単です。
継承元クラスRecyclerView.ViewHolderが引数に求めているのはBindingではなくViewなので、binding.rootを渡してあげましょう。

ここでAdapterを継承させるクラスも作成してしまいます。
MainActivityがあるフォルダで右クリック、New>Kotlin Class/Fileを選び、
クラス名を"MyItemAdapter"にして作成します。その中に今のクラスを入れてしまいましょう。

```:MyItemAdapter.kt
class MyItemAdapter {
    class MyViewHolder(val binding: ListItemBinding): RecyclerView.ViewHolder(binding.root)
}
```

## RecyclerView.Adapterを継承したクラス
さっき作ったクラスに継承させましょう。
このときに、ViewHolderを指定する必要があるので、このクラス内にあるMyViewHolderを渡してあげます。

```:MyItemAdapter.kt
class MyItemAdapter: RecyclerView.Adapter<MyItemAdapter.MyViewHolder>() {
    class MyViewHolder(val binding: ListItemBinding): RecyclerView.ViewHolder(binding.root)
}
```
次に、表示させたいアイテムのリストを引数に与えます。
与えるリストはListでもArrayListでもMutableListでもいいですし、
渡すデータもStringでもIntでも自作クラスでもいいです。
今回はStringのMutableListを渡します。
```:MyItemAdapter.kt
class MyItemAdapter(val itemList: MutableList<String>): RecyclerView.Adapter<MyItemAdapter.MyViewHolder>() {
    class MyViewHolder(val binding: ListItemBinding): RecyclerView.ViewHolder(binding.root)
}
```

ここでMyItemAdapterに赤線が引かれてると思います。
これはAdapterに必要なクラスを実装していないためです。
赤線にカーソルをあてると"Implement members"と出ると思うので、そこをクリック。(出なければ"More actions..."内を探すか手動でoverrideしてください。)
ShiftやらCtrlやらで全て選択してOKを押しましょう。

```:MyItemAdapter.kt
class MyItemAdapter(val itemList: MutableList<String>): RecyclerView.Adapter<MyItemAdapter.MyViewHolder>() {
    class MyViewHolder(val binding: ListItemBinding): RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        TODO("Not yet implemented")
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        TODO("Not yet implemented")
    }

    override fun getItemCount(): Int {
        TODO("Not yet implemented")
    }
}
```
このようになったところで各メソッドの中身を書いていきます。

::: note warn
TODO(...)は消さないとコンパイルエラーになります
:::

### onCreateViewHolder
ViewHolderにレイアウトを格納して返す役割です。
先程作ったレイアウトが初期化される際に一度だけ呼び出されます。
とにかくViewHolder返せればいいです。
```:MyItemAdapter.kt
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
    val inflater = LayoutInflater.from(parent.context)
    val binding = ListItemBinding.inflate(inflater, parent, false)
    return MyViewHolder(binding)
}

```
Inflaterについては詳しくは触れないので必要となったら調べてみてください。

### onBindViewHolder
ここが最も重要な部分です。
アイテム描画時に固有の値を当てはめる処理をここで行います。
holderにはレイアウトが、positionにはリスト内の何番目の要素かが格納されています。


```:MyItemAdapter.kt
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
    val item = itemList[position]
    holder.binding.itemText1.text = position.toString()
    holder.binding.itemText2.text = item
}
```
レイアウトの1行目にインデックス、2行目にアイテムを表示させます。
itemListはStringのMutableListだったのでそのまま要素を代入しています。

### getItemCount
リストのサイズさえ返せばいいです。

```:MyItemAdapter.kt
override fun getItemCount(): Int{
    return itemList.size
}

//override fun getItemCount() = itemList.size //なんならこれでいい
```

### MyItemAdapter完成
これでやっとAdapterの完成です。以下のようになっているでしょうか。

```:MyItemAdapter.kt
class MyItemAdapter(val itemList: MutableList<String>): RecyclerView.Adapter<MyItemAdapter.MyViewHolder>() {

    class MyViewHolder(val binding: ListItemBinding): RecyclerView.ViewHolder(binding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = ListItemBinding.inflate(inflater, parent, false)
        return MyViewHolder(binding)
    }

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val item = itemList[position]
        holder.binding.itemText1.text = position.toString()
        holder.binding.itemText2.text = item
    }

    override fun getItemCount() = itemList.size
}
```
ここまで来てやっと使えます、、お疲れ様です。

# Activity, Fragmentでの使用
自分の配置したいレイアウトにRecyclerViewを置きましょう。
Palette>Containers>RecyclerViewをドラックアンドドロップでもいいですし、xmlで記述してもいいです。
```:activity_main.xml
<?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="match_parent"
    tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/myRecyclerView"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintStart_toStartOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
```
そしたらソースコードで最後に自分の作ったAdapterなどを当てはめます。
```:MainActivity.kt
class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        /* 30個の文字列を格納したmutableList */
        val itemList = mutableListOf<String>()
        for(i in 1 .. 30){
            itemList.add("${i}個目のアイテム")
        }
        
        /* RecyclerViewにセット */
        binding.myRecyclerView.setHasFixedSize(true)
        binding.myRecyclerView.adapter = MyItemAdapter(itemList)
        binding.myRecyclerView.layoutManager = LinearLayoutManager(this)
    }
}
```
setHasFixedSizeというのは、RecyclerViewのサイズが変わる可能性がある場合falseにしますが、そのような用途がない場合処理が軽くなるtrueを選びましょう。
adapterには先程自作したものを設定します。
layoutManagerというのは、アイテムの並べ方を決めるものです。今回のようにリスト形式を目標とする場合、LinearLayoutManagerを使用しましょう。

# 完成
おめでとうございます。これでやっとRecyclerViewを実装できました!
今回は最もシンプルな構造なので、自分好みにカスタマイズしてみてください。
クリックリスナー等の実装は少しややこしいのでまた別記事にします！

# 実用例
このメモアプリをRecyclerViewを使用して作りました！
メモ一覧部分、カテゴリ選択部分、カテゴリフィルター部分にRecyclerViewを使用しています。よろしければご参考に！
> [スムーズメモ - 長～～～いスクリーンショットで簡単共有](https://play.google.com/store/apps/details?id=com.smoothapp.smoothnote)


# 参考文献
> [Android Developers: RecyclerView で動的リストを作成する](https://developer.android.com/guide/topics/ui/layout/recyclerview?hl=ja)