複雑な画面をRecyclerViewで作るEpoxy - Android

Androidで複雑な画面を作る際に、Airbnbが提供しているEpoxyというライブラリが良い感じだったので紹介します。

この記事はShibuya.apkで発表した、今更ながらEpoxyを元に書いています。 見ていただけるとありがたいです。

今更ながらEpoxy / Lateinit Epoxy - Speaker Deck

また、Epoxyを触った際に作ったレポジトリもGithubで公開しているので、そちらも参考にしてください。

github.com

Epoxyとは?

github.com

  • 複雑な画面をRecyclerViewで作るためのライブラリ
  • Airbnbが作成している
  • 今でも結構活動している
  • 2016/08/25に初リリース
  • androidx対応

ざっくり言うと、各Section(Row)毎のレイアウトを組んで順番に並べれば、それをRecyclerViewの再利用性を活用しつつ簡単に表示してくれる。というものです。

Airbnbが紹介記事を出しているので、是非こちらをお読みください。

medium.com

似たようなものとして、最近ではGroupieも有名です。自分はGroupieを使ったことがないので比較については書きません。

AirbnbEpoxyの他にも、MvRxPairsなどのライブラリを出していて、それらとの相性も良いです。

メリット

  • 簡潔に複雑な画面を作ることができる
  • RecyclerViewを使うことによって、各Viewが表示される直前にロードされるためパフォーマンスが良い
  • Viewの状態を保存しているので、Carouselなどのスクロール位置を画面外にいっても覚えている
  • 差分更新によって滑らかなアニメーションが実現できる

大まかな流れ

・セルのレイアウトを作成

.xml, .ktどちらからでも作成可能ですが、今回はより楽なXMLファイルとdatabindingを用いた方法で作ってみます。.ktで作成したい場合はこちらを参考にしてください。

・セルからEpoxyModelを自動生成

作成したセルをpackage-info.java内で指定することで、EpoxyRecyclerViewで使用するEpoxyModelXML(.kt)から自動生成します。

・Controllerを作成

RecyclerViewでいう所のAdapterとなる、Controllerを作成します。ここで、各セルの表示順序、データ、OnClick()等を設定します。

EpoxyRecyclerViewControllerをセット

最後に、EpoxyRecyclerViewをViewに追加して、Controllerをセットして完成です。

導入

それでは実際に以下のような画面を作ってみたいと思います。

1. 準備

app build.gradle

apply plugin: 'kotlin-kapt'

kapt {
    correctErrorTypes = true
}
dependencies {
  implementation 'com.airbnb.android:epoxy:3.x.y'
  implementation 'com.airbnb.android:epoxy-databinding:3.x.y'
  kapt 'com.airbnb.android:epoxy-processor:3.x.y'
}

x, yには最新のバージョンを入れてください。(投稿時は3.3.0

package-info.java

EpoxyModelをどのXMLファイルから作るか記述するものです。XMLファイルのみでdatabindingを使用してEpoxyModelを作る際に必要になります。

.kt (or .java)ファイルを置くフォルダの直下に作成してください。

@EpoxyDataBindingLayouts({R.layout.header_view, ... // other layouts })
package com.airbnb.epoxy.sample;

import com.airbnb.epoxy.EpoxyDataBindingLayouts;

これは下記のように接頭辞での指定も可能です。

@EpoxyDataBindingPattern(rClass = R.class, layoutPrefix = "view_holder")
package com.example.package;

2. セルの準備

これは特に変わったことはありません。databindingを使ってレイアウトを組む時と同じです。双方向のdatabindingは出来ないことに注意しましょう。

3. Controllerの作成

Controllerを作成する際はTypedEpoxyController<T>クラスを継承します。このTは中身のデータの型です。二つ渡したいときはTyped2EpoxyController<T, U>というクラスがあります。詳しくはこちらに記載されています。

Controllerでやることは、buildModels(data:)を実装することです。コードをみた方が早いと思うので今回作ったものを載せます。

2. セルの準備で作成したレイアウトをpackage-info.javaに記載後、BuildするとEpopxyModelが自動生成されます。

名前は、

R.layout.~による指定 -> そのまま
ex) R.layout.epoxy_header_view -> EpoxyHeaderViewBindingModel_.java
接頭辞による指定  -> 接頭辞以降
ex) (layoutPrefix = "epoxy")
R.layout.epoxy_header_view -> HeaderViewBindingModel_.java

といったようになります。

これを用いてbuildModels(data:)に記述していくのですが、便利なExtensionがあり、次のように記述出来ます。(今の所は...)

ここでidはViewを一意に判断するための識別子で、その後のciryName, descriptionXMLファイルで書いた<data />タグの中身です。

このようなViewのブロックを順に並べることで、その順通りに表示されます。

4. EpoxyRecyclerViewControllerをセット

残りはViewにEpoxyRecyclerViewを追加して、作成したControllerをセットして完成です。セットにはEpoxyRecyclerView.setController(controller:)を使って、controller.setData(data:)でデータを流します。

加えて..

EpoxyVisibilityTracker

スクロールイベントとして、それぞれのRowが表示されているか否かどれくらい表示されているかを引数としたコールバックを設定できます。こちらに詳しいことが記載されています。

val epoxyVisibilityTracker = EpoxyVisibilityTracker()
epoxyVisibilityTracker.attach(recyclerView)

Carousel

Epoxyの利点として、Carouselが標準で付いていることは大きいです。

導入はとても簡単で、以下のExtensionを追加すれば今までのRowと同様に導入することができます。詳しいことは公式ドキュメントで読んでください。

numViewsToShowOnScreen(num:)padding(padding:)でアイテムの間隔等を決めます。

padding(padding:)の引数としてPadding.dp(padding: itemSpacing:)を渡します。それぞれの変更箇所は図の通りです。現状ではitemSizeを指定してレイアウトを決めるということはできないようです。(2019/2/24)

ピッタっと止めるようにするには、RecyclerViewでも使われているSnapHelperFactoryを作成して、Carousel.setDefaultGlobalSnapHelperFactory(factory:)を呼び出すことで変更できます。詳しくはこちらで。

注意点として...

CoordinatorLayoutなどと一緒に使用する際(Nested Scrollが発生する際)は注意が必要です。

github.com

最後に

複雑な画面を作る際に、ScrollView + Fragmentで実装するとパフォーマンスも可読性も落ちてしまう場合があります。

複雑な画面を実装するのに苦労している時は、EpoxyやGroupieなどのRecyclerViewを活用するライブラリを用いて、より簡潔に簡単に作って楽になりましょう!

(次はMvRxについて書こうと思います....)