添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
11
6

More than 3 years have passed since last update.

[Android]RecyclerView の ConcatAdapter を使う方法

Posted at

例えば今までの RecyclerView.Adapter を利用した実装では "ヘッダーを表示する View" と "コンテンツを表示する View" を組み合わせて表示するとなると大変だったんですが ConcatAdapter を利用すると簡単に実装できるらしいです。

ConcatAdapter を利用して "ヘッダーを表示する View" と "コンテンツを表示する View" を組み合わせた表示どうやったらできるのか以下のサンプルを作って調べたのでまとめたいと思います。

セットアップ

ConcatAdapter は Version 1.2.0-alpha03 から MergerAdapter という名称で追加されたあと、Version 1.2.0-alpha04 にて今の ConcatAdapter という名称に変更されました。なので ConcatAdapter を使うには Version 1.2.0-alpha04 以上のバージョンを使う必要があります。今回は現時点での最新版である Version 1.2.0-rc01 を使っていこうかと思います。

dependencies {
    implementation "androidx.recyclerview:recyclerview:1.2.0-rc01"
表示するデータを宣言する

User という firstName と lastName, age を持つデータクラスと Category という title を持つデータクラスを定義します。今回はこの User と Category に定義したデータを ConcatAdapter を利用して一つの RecyclerView で表示できるようにします。

data class User(val firstName: String, val lastName: String, val age: Int) {
    val id = UUID.randomUUID().toString()
data class Category(val title: String) {
    val id = UUID.randomUUID().toString()
表示する View を宣言する

今回は viewBinding を利用したいので buildFeatures で viewBinding を true にしておきます。

android {
    buildFeatures {
        viewBinding true

Category を表示する View を宣言します。layout_category_item.xml というファイルに以下の定義をして View として title を表示できるようにしておきます。

<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    xmlns:tools="http://schemas.android.com/tools">
    <TextView
        android:id="@+id/category_title_text_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textSize="48sp"
        tools:text="CATEGORY" />
</FrameLayout>

User を表示する View を宣言しておきます。layout_user_item.xml というファイルに以下の定義をして定義して firstName や lastName, age を表示できるようにしておきます。

<?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="wrap_content"
    android:layout_margin="8dp"
    android:background="#eeeeee"
    android:padding="8dp">
    <TextView
        android:id="@+id/age_text_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="64sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toEndOf="@id/first_name_text_view"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="100" />
    <TextView
        android:id="@+id/first_name_text_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:textSize="32sp"
        app:layout_constraintBottom_toTopOf="@id/last_name_text_view"
        app:layout_constraintEnd_toStartOf="@id/age_text_view"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        tools:text="FIRST NAME" />
    <TextView
        android:id="@+id/last_name_text_view"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:textSize="32sp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toStartOf="@id/age_text_view"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toBottomOf="@id/first_name_text_view"
        tools:text="LAST NAME" />
</androidx.constraintlayout.widget.ConstraintLayout>
表示を制御するアダプターを作成する

ConcatAdapter に追加する RecyclerView.Adapter を作成します。Category を表示する CategoryAdapter、User を表示する UserListAdapter の 2つの RecyclerView.Adapter を定義します。

class CategoryItemViewHolder(
    private val binding: LayoutCategoryItemBinding
) : RecyclerView.ViewHolder(binding.root) {
    fun bind(category: Category) {
        binding.categoryTitleTextView.text = category.title
val DIFF_UTIL_CATEGORY_ITEM_CALLBACK = object : DiffUtil.ItemCallback<Category>() {
    override fun areContentsTheSame(oldItem: Category, newItem: Category): Boolean {
        return oldItem == newItem
    override fun areItemsTheSame(oldItem: Category, newItem: Category): Boolean {
        return oldItem.id == newItem.id
class CategoryAdapter : ListAdapter<Category, CategoryItemViewHolder>(DIFF_UTIL_CATEGORY_ITEM_CALLBACK) {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CategoryItemViewHolder {
        val view = LayoutCategoryItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return CategoryItemViewHolder(view)
    override fun onBindViewHolder(holder: CategoryItemViewHolder, position: Int) {
        holder.bind(getItem(position))
class UserItemViewHolder(
    private val binding: LayoutUserItemBinding
) : RecyclerView.ViewHolder(binding.root) {
    fun bind(user: User) {
        binding.firstNameTextView.text = user.firstName
        binding.lastNameTextView.text = user.lastName
        binding.ageTextView.text = user.age.toString()
val DIFF_UTIL_USER_ITEM_CALLBACK = object : DiffUtil.ItemCallback<User>() {
    override fun areContentsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem == newItem
    override fun areItemsTheSame(oldItem: User, newItem: User): Boolean {
        return oldItem.id == newItem.id
class UserListAdapter : ListAdapter<User, UserItemViewHolder>(DIFF_UTIL_USER_ITEM_CALLBACK) {
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserItemViewHolder {
        val view = LayoutUserItemBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return UserItemViewHolder(view)
    override fun onBindViewHolder(holderUser: UserItemViewHolder, position: Int) {
        holderUser.bind(getItem(position))
ConcatAdapter を使ってデータを表示する

あとは RecyclerView を定義して、作成した RecyclerView.Adapter を ConcatAdapter にセットすればOKです。

  • ConcatAdapter.add にて ReyclerView.Adapter を追加する
  • ConcatAdapter.add で追加した RecyclerView.Adapter の submitList を呼び出す
  • class MainActivity : AppCompatActivity() {
        private lateinit var binding: ActivityMainBinding
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(binding.root)
            setupConcatAdapter()
            val categoryAdapter = CategoryAdapter()
            val userListAdapter = UserListAdapter()
            val concatAdapter = ConcatAdapter().apply {
                addAdapter(categoryAdapter)
                addAdapter(userListAdapter)
            binding.recyclerView.adapter = concatAdapter
            binding.recyclerView.layoutManager =
                LinearLayoutManager(applicationContext, RecyclerView.VERTICAL, false)
            categoryAdapter.submitList(listOf(Category("人名図鑑")))
            userListAdapter.submitList(
                listOf(
                    User("あいざわ", "かずき", 29),
                    User("ふじくら", "まさひろ", 52),
                    User("よしずみ", "ひろゆき", 54),
                    User("ほりのうち", "しんいち", 40),
                    User("はすぬま", "よしろう", 37),
                    User("はなわ", "のぶお", 38),
                    User("おじま", "おじま", 31),
                    User("しんざき", "くにひと", 35)
    
    <?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">
        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:padding="8dp" />
    </androidx.constraintlayout.widget.ConstraintLayout>
    

    どうやら ConcatAdapter.add を実行した順で表示されるようです。例えば次のように UserListAdapter を追加した後に CategoryAdapter を追加すると順番が逆になります。

    class MainActivity : AppCompatActivity() {
        private lateinit var binding: ActivityMainBinding
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            binding = ActivityMainBinding.inflate(layoutInflater)
            setContentView(binding.root)
            setupConcatAdapter()
            val categoryAdapter = CategoryAdapter()
            val userListAdapter = UserListAdapter()
            val concatAdapter = ConcatAdapter().apply {
                addAdapter(userListAdapter)
                addAdapter(categoryAdapter)
            binding.recyclerView.adapter = concatAdapter
            binding.recyclerView.layoutManager =
                LinearLayoutManager(applicationContext, RecyclerView.VERTICAL, false)
            categoryAdapter.submitList(listOf(Category("人名図鑑")))
            userListAdapter.submitList(
                listOf(
                    User("あいざわ", "かずき", 29),
                    User("ふじくら", "まさひろ", 52),
                    User("よしずみ", "ひろゆき", 54),
                    User("ほりのうち", "しんいち", 40),
                    User("はすぬま", "よしろう", 37),
                    User("はなわ", "のぶお", 38),
                    User("おじま", "おじま", 31),
                    User("しんざき", "くにひと", 35)
    

    というように ConcatAdapter を使うと簡単にヘッダー付きやフッダー付きの RecyclerView を作成できます。以前はヘッダーやフッダーをつけようとすると RecyclerView.Adapter が複雑化してしまうことがありましたがこれなら簡単にできるかなと思います。

    しかし ConcatAdapter は RecyclerView.Adapter を結合するだけなので複雑な表示をしたいならば Epoxy や Groupie を使うのが良さそうだと思いました。まだまだ公式が提供する Adapter だけでは実装が難しい表示があるのは変わらないですね。

    11
    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
    Sign up Login
    11
    6