🤖

PoolingContainerでRecyclerView内のComposeViewを上手に再利用する方法

2022/06/09に公開

概要

前回の記事でJetpack Composeを導入する際にComposeViewを使って、RecyclerViewを使うハイブリッドな方法は、RecyclerViewだけ/Jetpack Composeだけで利用するより、パフォーマンスがよく無さそうという結果が出ました。

ですが、前回の方法では、スクロールのたびにsetContentView()を呼び出し、都度コンポーザブルを作っていたのでViewHolderの再利用という点でパフォーマンスが悪いのかもしれません。

で、recyclerview:1.3.0-alpha02からPoolingContainerを使って再利用が可能になったようなので、試してみます。
(ただし、正直、ドキュメントがなく正しい実装方法なのかがいまいちわからず実装しています。ので、間違っている可能性もあります)

準備

以下のライブラリを入れておきます。

    implementation "androidx.customview:customview:1.1.0"
    implementation "androidx.customview:customview-poolingcontainer:1.0.0-beta02"
    implementation "androidx.recyclerview:recyclerview:1.3.0-alpha02"

実装

まずはComposeViewを作成します。カスタムなComposeViewを作成するのでAbstractComposeViewを継承して作ります。
動的な部分のItemを保持しています。

ComposeListItemViewは、前回の記事でも書いたリストのアイテムをデザインしたコンポーザブル関数です。

class ComposeViewListItemView @JvmOverloads constructor(
  context: Context,
  attrs: AttributeSet? = null,
  defStyle: Int = 0
) : AbstractComposeView(context, attrs, defStyle) {
  var item by mutableStateOf(Item("",""))

  @Composable
  override fun Content() {
    key(item) {
      ComposeListItemView(item)
    }
  }
}

layoutファイルで上記を指定しています。compose_view_poolingというid です(=ComposeViewPooling)。

<?xml version="1.0" encoding="utf-8"?>
<com.ko2ic.spike.ui.view.ComposeViewListItemView
    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:id="@+id/compose_view_pooling"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

次にRecyclerView.AdapterとRecyclerView.ViewHolderの実装です。

class RecyclerViewWithPoolingComposeAdapter(private val listItems: List<Item>) :
  RecyclerView.Adapter<PoolingComposeItemViewHolder>() {

  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): PoolingComposeItemViewHolder {
    val binding = ListItemViewComposePoolingBinding.inflate(
      LayoutInflater.from(parent.context), parent, false
    )
    return PoolingComposeItemViewHolder(binding)
  }

  override fun onBindViewHolder(holder: PoolingComposeItemViewHolder, position: Int) {
    holder.bindView(listItems[position])
  }

  override fun getItemCount(): Int {
    return listItems.size
  }
}

class PoolingComposeItemViewHolder(
  viewBinding: ListItemViewComposePoolingBinding
) : RecyclerView.ViewHolder(viewBinding.root) {
  private val listItemView: ComposeViewListItemView = viewBinding.composeViewPooling

  fun bindView(content: Item) {
    listItemView.item = content
  }
}

ポイントは、PoolingComposeItemViewHolderクラスで、ComposeViewListItemViewをフィールドで持ち、onBindViewHolder()で、動的に変わるItemだけを渡している点です。
これで、ViewHolderを再利用するときにComposeViewListItemViewは再度作られないはずです。

結果

PoolingContainerを使っても代わりませんでした。まだbeta版なので今後改善されるか、もしくは実装の仕方が悪いのかもしれません。

PoolingContainer利用 スクロールのたびにsetContentView()
PoolingContainer RecyclerViewとComposeView
NewsPicks の Zenn

Discussion