【RecyclerView × ViewDataBinding】なるべく頭を使わずに最短で複数レイアウトを使う

https://github.com/yuskey38/example-multi-layout-recycler-adapter/tree/main/app/src/main/java/com/example/example_multi_layout_recycler_view

## やりたいこと
なるべく頭を使わずに最短でRecyclerViewで複数レイアウトを使う。
いつも忘れる + ググった時にやり方色々出てきてややこしいのでテンプレート作っておく。

## 実装方法の大枠
基本的に最後に添付したコードをまるっとコピペして少し修正すればOK。
データやレイアウトは自由に作って試してみてください。

① ViewTypeのEnumを作る
② `onCreateViewHolder`はコピペ
③ `onBindViewHolder`もほぼコピペ
④ `getItemViewType`もほぼコピペ
⑤ `getItemCount`もほぼコピペ
⑥ ViewHolderのメソッドでデータとレイアウトを紐づける

※ 上記の順番で実装していく場合は`ViewHolder`がないよとエラーになりますが、最後までいけば解消されるのでスルーしてください。

## 元となるAdapterファイル
```kotlin
data class Layout1Data(
    val country: String,
    val city: String
)

data class Layout2Data(
    val name: String,
    val number: Int
)

class MultiLayoutRecyclerAdapter :
    RecyclerView.Adapter<MultiLayoutRecyclerAdapter.ViewHolder>() {

    private val list = listOf(
        Layout1Data("Japan", "Tokyo"),
        Layout2Data("Alice", 1),
        Layout2Data("Bob", 2),
        Layout2Data("Cathy", 3),
        Layout2Data("Daniel", 4),
        Layout2Data("Elza", 5),
        Layout2Data("Fiona", 6),
        Layout2Data("Gina", 7),
        Layout2Data("Helen", 8),
    )
}
```
色々な実装方法があってややこしいので、下記の`$クラス`だけ変更すればOKになるように実装していきます。
`class $クラス: RecyclerView.Adapter<$クラス.ViewHolder>()`

## ① ViewTypeのEnumを作る
```kotlin
private enum class ViewType(val layoutId: Int) {
    LAYOUT_1(R.layout.layout_1),
    LAYOUT_2(R.layout.layout_2),
}
```
viewTypeに応じてレイアウトを出し分けたいのでとプロパティにレイアウトIDを設定しておきます。
なおprivateにしているのでクラス名("ViewType")をファイルごとに変更する必要はありません。

## ② `onCreateViewHolder`はコピペ

```kotlin
override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
    val view: ViewDataBinding = DataBindingUtil.inflate(
        LayoutInflater.from(viewGroup.context),
        ViewType.values()[viewType].layoutId,
        viewGroup,
        false
    )

    return ViewHolder(view)
}
```
①で作成したEnumを使用してコピペを実現します。
`ViewType.values()[viewType].layoutId`でviewTypeに応じたレイアウトIDを指定しています。


## ③ `onBindViewHolder`もほぼコピペ
```kotlin
override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
    when(val data = list[position]) {
        is Layout1Data -> viewHolder.bind(data)
        is Layout2Data -> viewHolder.bind(data)
        else -> return
    }
}
```
listに渡したデータに応じてそれぞれのレイアウトにデータをバインドしていきます。

## ④ `getItemViewType`もほぼコピペ
```kotlin
override fun getItemViewType(position: Int): Int {
    return when(list[position]) {
        is Layout1Data -> ViewType.LAYOUT_1.ordinal
        is Layout2Data -> ViewType.LAYOUT_2.ordinal
        else -> ViewType.LAYOUT_2.ordinal
    }
}
```
listに渡したデータに応じてItemViewのViewTypeを指定します。

## ⑤ `getItemCount`もコピペ
```kotlin
override fun getItemCount() = list.size
```
listのサイズを指定します

## ⑥ ViewHolderのメソッドでデータとレイアウトを紐づける
```kotlin
inner class ViewHolder(private val binding: ViewDataBinding) :
    RecyclerView.ViewHolder(binding.root) {

    fun bind(data: Layout1Data) {
        (binding as Layout1Binding).run {
            country.text = data.country
            city.text = data.city
        }
    }

    fun bind(data: Layout2Data) {
        (binding as Layout2Binding).run {
            name.text = data.name
            name.setTextColor(Color.BLUE)
            number.text = data.number.toString()
        }
    }
}
```
`fun bind(data: $データクラス)`をレイアウトの数だけ用意します。
各bindメソッド内でレイアウトとデータを紐づけていけば完了です。
なお、innerにしてるのでクラス名("ViewHolder")をファイルごとに変更する必要はありません。


## コピペ用
```kotlin
data class Layout1Data(
    val country: String,
    val city: String
)

data class Layout2Data(
    val name: String,
    val number: Int
)

class MultiLayoutRecyclerAdapter :
    RecyclerView.Adapter<MultiLayoutRecyclerAdapter.ViewHolder>() {

    private val list = listOf(
        Layout1Data("Japan", "Tokyo"),
        Layout2Data("Alice", 1),
        Layout2Data("Bob", 2),
        Layout2Data("Cathy", 3),
        Layout2Data("Daniel", 4),
        Layout2Data("Elza", 5),
        Layout2Data("Fiona", 6),
        Layout2Data("Gina", 7),
        Layout2Data("Helen", 8),
    )

    private enum class ViewType(val layoutId: Int) {
        LAYOUT_1(R.layout.layout_1),
        LAYOUT_2(R.layout.layout_2),
    }

    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): ViewHolder {
        val view: ViewDataBinding = DataBindingUtil.inflate(
            LayoutInflater.from(viewGroup.context),
            ViewType.values()[viewType].layoutId,
            viewGroup,
            false
        )

        return ViewHolder(view)
    }

    override fun onBindViewHolder(viewHolder: ViewHolder, position: Int) {
        when(val data = list[position]) {
            is Layout1Data -> viewHolder.bind(data)
            is Layout2Data -> viewHolder.bind(data)
            else -> return
        }
    }

    override fun getItemViewType(position: Int): Int {
        return when(list[position]) {
            is Layout1Data -> ViewType.LAYOUT_1.ordinal
            is Layout2Data -> ViewType.LAYOUT_2.ordinal
            else -> ViewType.LAYOUT_2.ordinal
        }
    }

    override fun getItemCount() = list.size

    inner class ViewHolder(private val binding: ViewDataBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun bind(data: Layout1Data) {
            (binding as Layout1Binding).run {
                country.text = data.country
                city.text = data.city
            }
        }

        fun bind(data: Layout2Data) {
            (binding as Layout2Binding).run {
                name.text = data.name
                name.setTextColor(Color.BLUE)
                number.text = data.number.toString()
            }
        }
    }
}
```