7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

[Android]Epoxy が StickyHeader に対応しているらしいので試してみた

Posted at

はじめに

RecyclerView の Adapter の実装の部分を楽にしてくれるライブラリの Epoxy ですが、
どうやら Sticky Header にも対応してくれているらしいです。今回は Epoxy で Sticky Header をどのような感じで利用できるのか紹介したいと思います。

準備する

Sticky Header が利用できるようになったのは 3.10.0 からみたいです。なので次の内容を build.gradle に記述して、3.10.0 以上の Epoxy を使えるようにします。

Image from Gyazo

// kapt 有効化
apply plugin: 'kotlin-kapt' 

android {
      
    // databinding 有効化
    dataBinding {
        enabled = true 
    }
      
}

dependencies {
      
    def epoxy_version = "3.11.0"
    implementation "com.airbnb.android:epoxy:$epoxy_version"
    implementation "com.airbnb.android:epoxy-databinding:${epoxy_version}"
    kapt "com.airbnb.android:epoxy-processor:$epoxy_version"
      
}

実装する

EpoxyModel を作成する

まずは EpoxyModel を作成していきます。今回は StickyHeader になる HeaderLayout と StickyHeader にならない ContentLayout と分けて作成していきます。

HeaderLayout

Image from Gyazo

<?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>

    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{title}"
            android:textColor="@android:color/white"
            android:textSize="32sp"
            android:background="@color/colorPrimary"
            tools:text="Sticky Header" />

    </androidx.appcompat.widget.LinearLayoutCompat>
</layout>

ContentLayout

Image from Gyazo

<?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>

    <androidx.appcompat.widget.LinearLayoutCompat
        android:layout_width="match_parent"
        android:layout_height="wrap_content">

        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="@{title}"
            tools:text="Content" />

    </androidx.appcompat.widget.LinearLayoutCompat>
</layout>

package-info.java
そしてこのままだと Epoxy は EpoxyModel を生成してくれません。ですので package-info.java を作成して Epoxy が EpoxyModel を生成してくれるようにしてやります。

@EpoxyDataBindingLayouts({R.layout.content_layout, R.layout.header_layout})
package jp.kaleidot725.sample;
import com.airbnb.epoxy.EpoxyDataBindingLayouts;

EpoxyController を作成する

次に EpoxyController を作成してやります、今回は単純に先程作成した HeaderLayout と ContentLayout を交互に表示する StickyHeaderController というクラスを作成してやります。

data class Content(val uuid: String, val value: String)
data class Header(val uuid: String,val value: String)

class StickyHeaderController : Typed2EpoxyController<List<Header>, List<Content>>() {
    override fun buildModels(headers: List<Header>, contents: List<Content>) {
        headers.forEach { header ->
            headerLayout {
                id(header.uuid)
                title(header.value)
            }

            contents.forEach { content ->
                contentLayout {
                    id(content.uuid)
                    title(content.value)
                }
            }
        }
    }
}

EpoxyRecyclerView をセットアップする

あとは EpoxyRecyclerView を定義してセットアップしてやります。

MainActivity

<?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">

    <com.airbnb.epoxy.EpoxyRecyclerView
        android:id="@+id/epoxy_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>
class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val stickyHeaderController = StickyHeaderController()
        epoxy_recycler_view.adapter = stickyHeaderController.adapter
        stickyHeaderController.setData(createHeaders(10), createContents(100))
    }

    private fun randomUUIDString(): String {
        return UUID.randomUUID().toString()
    }

    private fun createHeaders(max: Long): List<Header> {
        return (0..max).map { count -> Header(randomUUIDString(), "Header $count") }
    }

    private fun createContents(max: Long): List<Content> {
        return (0..max).map { count -> Content(randomUUIDString(), "Content $count") }
    }
}

ここまで実装したものだと通常の Epoxy と同じで StickyHeader の動作になりません。 StickyHeader として使うには次の実装を追加してやる必要があります。

Image from Gyazo

StickyHeader として動作するようにする

StickyHeader として動作させるには次の 2つの実装を追加してやります。

  1. EpoxyRecyclerView の layoutManager として StickyHeaderLinearLayoutManager をセットしてやる
  2. EpoxyRecyclerView の adpter としてセットされる EpoxyController の isStickyHeader を override してやる

EpoxyRecyclerView の layoutManager として StickyHeaderLinearLayoutManager をセットしてやる

StickyHeader を利用するにはまず RecyclerView の layoutManager に StickyHeaderLinearLayoutManager をセットしてやる必要があります。この StickyHeaderLinearLayoutManager が StickyHeader である EpoxyModel を見つけてくれるようになっていて、RecyclerView のスクロール状況に応じて StickyHeader を貼り付けてくれます。

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val stickyHeaderController = StickyHeaderController()
        epoxy_recycler_view.adapter = stickyHeaderController.adapter
        epoxy_recycler_view.layoutManager = StickyHeaderLinearLayoutManager(applicationContext)
        stickyHeaderController.setData(createHeaders(10), createContents(100))
    }
}

EpoxyRecyclerView の adpter としてセットされる EpoxyController の isStickyHeader を override してやる

StickyHeaderLinearLayoutManager では EpoxyController の isStickyHeader を利用して StickyHeader を識別するような仕組みになっています。ですので isStickyHeader を override して自身が定義した EpoxyModel を StickyHeader として認識されるようにします。(isStickyHeader の引数として Position が渡されます、なので Position にある EpoxyModel のクラスを取得して、StickyHeaderとして扱いたいクラスであるか判定してやります。)

class StickyHeaderController : Typed2EpoxyController<List<Header>, List<Content>>() {
        
    override fun isStickyHeader(position: Int): Boolean {
        return adapter.getModelAtPosition(position)::class == HeaderLayoutBindingModel_::class
    }
}

この実装を加えると EpoxyModel が StickyHeader として扱われ RecyclerView に StickyHeader が表示されるようになります。

Image from Gyazo

おわりに

という感じで Epoxy でも StickyHeader が使えるようになっているらしいです。使い方をまとめると次のようになりますね。

  • 通常の利用方法と同じで EpoxyModel と EpoxyController を実装してやる
  • RecyclerView の adapter に EpoxyController.Adapter、layoutManager に StickyHeaderLinearLayoutManager をセットしてやる
  • StickyHeaderLinearLayoutManager が StickyHeader である EpoxyModel を識別できるように EpoxyController.isStickyHeader を実装してやる。

参考文献

StickyHeader は現時点(2020/07/12)でまだドキュメントが整備されていないので、詳しく知りたい方は次のリンクを見てみると良いかなと思います。

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?