自作アプリでepoxyを使用したので、簡単に使用方法をまとめておきます。
Epoxyとは
Airbib製のライブラリです。RecyclerViewをそのまま使うのに比べ、複数パターンのレイアウトがある場合に簡単に切り替えられたり、表示データの更新でいい感じに差分更新をしてくれます(後述)。
https://github.com/airbnb/epoxy
今回は以下のような構造のデータをリスト表示する例を書いてみます
data class Foo (
var title: String,
var bar: List<Bar>
)
data class Bar (
var body: String,
var time: String
)
gradle
build.gradle(app)
apply plugin: 'kotlin-kapt' // kapt使用
android {
dataBinding {
enabled = true // data binding使います
}
}
kapt {
correctErrorTypes = true
}
dependencies {
implementation 'com.airbnb.android:epoxy:3.4.2'
kapt 'com.airbnb.android:epoxy-processor:3.4.2'
implementation 'com.airbnb.android:epoxy-databinding:3.4.2' // data binding使います
}
cellレイアウトの準備
list_cell_a.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
>
<data>
<variable
name="title"
type="String"
/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{title}"
android:textSize="24sp"
android:textStyle="bold"
tools:text="test"
/>
</LinearLayout>
</layout>
list_cell_b.xml
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
>
<data>
<variable
name="body"
type="String"
/>
<variable
name="time"
type="String"
/>
</data>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp"
>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{body}"
android:textSize="20sp"
tools:text="test"
/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{time}"
android:textSize="18sp"
tools:text="test"
/>
</LinearLayout>
</layout>
EpoxyConfig.java
パッケージフォルダのルートにこのファイルを配置することで、ビルド時にレイアウトファイルのクラスを{レイアウトファイルのキャメルケース}BindingModel_
の形式で自動生成してくれます。
package <your package name>;
import com.airbnb.epoxy.EpoxyDataBindingLayouts;
@EpoxyDataBindingLayouts({
R.layout.list_cell_a,
R.layout.list_cell_b
})
interface EpoxyConfig {}
EpoxyController
AdapterのかわりにTypedEpoxyControllerを継承したクラスを作成します。buildmodels
をoverrideし、レイアウト作成処理を実装します。
class FooBarController : TypedEpoxyController<Foo>() {
override fun buildModels(foo: Foo) {
ListCellABindingModel_()
.title(foo.title) // ←databindingのデータ
.id(modelCountBuiltSoFar)
.addTo(this)
foo.bar.forEach {
ListCellBBindingModel_()
.body(it.body)
.time(it.time)
.id(modelCountBuiltSoFar)
.addTo(this)
}
}
}
- buildModelsメソッドの引数の数に応じて、Typed2EpoxyController、Typed3EpoxyControllerを継承してください(クラス名もっとどうにかならなかったのだろうか)
- title、body、timeはレイアウトで設定したdatabindingのプロパティです。
- idは各セルに割り振られるIDです。このidが同じだとセルのデータ更新、idが異なるとセルの新規作成となります。例えば以下のように記載すると、セルは一つしか生成されません。
override fun buildModels(foo: Foo) {
ListCellABindingModel_()
.title(foo.title)
.id("id")
.addTo(this)
foo.bar.forEach {
ListCellBBindingModel_()
.body(it.body)
.time(it.time)
.id("id")
.addTo(this)
}
}
- 特に差分更新を意識する必要がない場合は、idを自動で割り振ってくれる
modelCountBuiltSoFar
を使うと良いみたいです
呼び出す画面
呼び出し側のlayoutファイルでは普通にRecyclerView
を宣言します。
class FooBarFragment : Fragment() {
lateinit var binding: FragmentFooBarBinding //←このフラグメントのレイアウトファイル
override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_foo_bar, container, false)
binding.recyclerView.layoutManager = LinearLayoutManager(this)
return binding.root
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
val controller = FooBarController()
binding.recyclerView.adapter = controller.adapter
val data = Foo("タイトル", listOf(Bar("本文1","時間1"),Bar("本文2","時間2")))
controller.setData(data)
})
}
- controller側のIDを適切に設定していれば、データの登録、更新全てで
setData
を呼べばいい感じにviewに反映してくれます。
所感
- viewHolderから開放されてスッキリ
- データ更新時のnotifyから開放されてスッキリ