【Kotlin】Epoxyを使ってみる

RecyclerViewと合わせて使うことで複雑なレイアウトのリストやアコーディオンを持つリストなど
凝ったUIを簡単に実装することができます。

[Epoxy](https://github.com/airbnb/epoxy)

この記事で使ったレイアウトファイルは最後にコード載せているのでコピペして使ってください。
この記事で作成した [サンプルコードはこちら](https://github.com/sEmoto0808/kt-Epoxy-samples)

**※DataBindingを使ってます。**
# インストール
 アプリレベルのBuild.gradleで

```Kotlin:build.gradle
dependency {

    // 省略

    // Epoxy
    implementation 'com.airbnb.android:epoxy:2.19.0'
    implementation 'com.airbnb.android:epoxy-databinding:2.19.0'
    kapt 'com.airbnb.android:epoxy-processor:2.19.0'  // <- Kotlinの場合
}
```

# 必要なもの
- Epoxyを適用するRecyclerView（EpoxyRecyclerView）
- RecyclerViewで表示するリストアイテムのCustomView（ファイル名にPrefixを付けると実装が楽）
- Epoxyを使って表示するCustomViewを指定するファイル（後述）
- TypedEpoxyControllerを継承したクラス

### Epoxyを使って表示するCustomViewを指定するファイル
**package-info.java**

```
// レイアウトファイルにPrefix（例：epoxy_view_holder）をつけていないと全て記述することになる
@EpoxyDataBindingPattern(rClass = R.class, layoutPrefix = "epoxy_view_holder")
package package名;  // <- アプリのパッケージ名

import com.airbnb.epoxy.EpoxyDataBindingPattern;
```
このファイルをApplicationクラスのファイルと同じ場所に置く。

### TypedEpoxyControllerを継承したクラス
このクラスでリストの表示やタップイベントなどを管理することになる。

# 実装の流れ
1. EpoxyRecyclerViewをセットする
2. RecyclerViewで表示するリストアイテムを作る
  - `epoxy_view_holder_text_header.xml`
  - `epoxy_view_holder_text_item.xml`
3. package-info.javaを追加
4. TypedEpoxyControllerを継承したクラスを実装する
5. 4.で実装したクラスを1.のEpoxyRecyclerViewにsetController()で渡す

1.と2.はこの記事の最後にコード載せているので参照してください。
3.は上記の通りです

### 4. TypedEpoxyControllerを継承したクラスを実装する

1. `buildModels()`に表示したいデータのリストを渡す。
2. `buildModels()`内で上から表示したい順に自作したCustomViewの`***BindingModel_`をaddする。


```Kotlin:SimpleEpoxyListController.kt
class SimpleEpoxyListController
    : Typed2EpoxyController<List<String>, List<String>>() {

    override fun buildModels(
        headers: List<String>?,
        items: List<String>?
    ) {

        headers ?: return
        items ?: return

        // １つ目のSection Header
        TextHeaderBindingModel_()
            .id(headers[0], 0.toLong())
            .text(headers[0])
            .addTo(this)

        // １つ目のitemリスト
        items.forEachIndexed { indexItem, text ->

            TextItemBindingModel_()
                .id("TextItemBindingModel_0", indexItem.toLong())
                .text(text)
                .addTo(this)
        }

        // ２つ目のSection Header
        TextHeaderBindingModel_()  // <- １つ目のSection Header
            .id(headers[1], 1.toLong())
            .text(headers[1])
            .addTo(this)

        // ２つ目のitemリスト
        items.forEachIndexed { indexItem, text ->

            TextItemBindingModel_()
                .id("TextItemBindingModel_1", indexItem.toLong())
                .text(text)
                .addTo(this)
        }
    }
}
```
上記の例だとこんな感じの画面になります。
![Screenshot_20190715_003133_com.example.semoto.epoxysamples.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/139470/30be540b-d9f8-ec56-7c27-7d21c20fe8c1.jpeg)


### 5. TypedEpoxyControllerをEpoxyRecyclerViewにsetController()で渡す

```kotlin:SimpleEpoxyFragment.kt

    // 省略

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        // 省略

        val controller = SimpleEpoxyListController()

        // setData()でデータを渡すとbuildModels()が呼ばれる
        controller.setData(
            listOf(
                "header1","header2"
            ),
            listOf(
                "A","B","C","D","E","F"
            )
        )

        // EpoxyRecyclerViewに4.で自作したTypedEpoxyControllerを継承したControllerをセット
        binding.simpleTextEpoxyRecyclerView.setController(controller)

        return binding.root
    }
```

# この記事で使ったレイアウトファイル
#### fragment_simple_epoxy.xml
```
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">

    <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="match_parent">

        <com.airbnb.epoxy.EpoxyRecyclerView
                android:id="@+id/simpleTextEpoxyRecyclerView"
                android:layout_width="0dp"
                android:layout_height="0dp"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"/>
    </android.support.constraint.ConstraintLayout>
</layout>
```
#### epoxy_view_holder_text_header.xml
```
<?xml version="1.0" encoding="utf-8"?>
<layout
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
                name="text"
                type="String"/>
    </data>

    <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">

        <TextView
                android:id="@+id/textSimpleHeader"
                android:layout_width="0dp"
                android:layout_height="50dp"
                android:text="@{text}"
                android:gravity="center"
                android:background="@android:color/holo_blue_light"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"/>

    </android.support.constraint.ConstraintLayout>
</layout>
```

#### epoxy_view_holder_text_item.xml
```
<?xml version="1.0" encoding="utf-8"?>
<layout
        xmlns:app="http://schemas.android.com/apk/res-auto"
        xmlns:android="http://schemas.android.com/apk/res/android">

    <data>
        <variable
                name="text"
                type="String"/>
    </data>

    <android.support.constraint.ConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content">


        <android.support.v7.widget.CardView
                android:id="@+id/textCard"
                android:layout_width="0dp"
                android:layout_height="wrap_content"
                android:layout_marginBottom="10dp"
                android:layout_marginStart="20dp"
                android:layout_marginEnd="20dp"
                app:layout_constraintTop_toTopOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent">

            <TextView
                    android:id="@+id/textSimple"
                    android:layout_width="100dp"
                    android:layout_height="50dp"
                    android:layout_gravity="center"
                    android:text="@{text}"
                    android:gravity="center"
                    app:layout_constraintTop_toTopOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintBottom_toBottomOf="parent"
                    app:layout_constraintEnd_toEndOf="parent"/>
        </android.support.v7.widget.CardView>
    </android.support.constraint.ConstraintLayout>
</layout>
```

今回の記事は以上です。