概要
巷によくあるアプリのパターンとして、画面の上に横長のバナーやレイアウトを置いて、その下に格子状のレイアウトを置くというものがあります。
このデザインに対応するためのよくある実装として、一つのRecylerAdapterで、ViewHolderを複数用意して、item毎にViewHolderを切り替えるといったものがあります。
しかし、ロジックが複雑化するので、実装コストや後のメンテナンスコストが増えてしまう原因でもあります。
今回はよくあるこのパターンを、recyclerviewのalpha版に実装されているMergeAdapterを使って実装していきます。
Sample
https://github.com/alpha2048/MergeAdapterTest

解説
MergeAdapterはrecyclerviewの1.2.0のalpha02から入っています。
implementation "androidx.recyclerview:recyclerview:1.2.0-alpha03"
実装はとても簡単です
- Banner用のAdapterを作成
- GridItem用のAdapterを作成
- MergeAdapterに上の2つをマージする
Banner用のAdapter
class BannerAdapter() : RecyclerView.Adapter<ItemBannerViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemBannerViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemListBannerBinding.inflate(layoutInflater, parent, false)
return ItemBannerViewHolder(binding)
}
override fun onBindViewHolder(holder: ItemBannerViewHolder, position: Int) {
}
override fun getItemCount(): Int {
return 1
}
}
class ItemBannerViewHolder(val binding: ItemListBannerBinding) : RecyclerView.ViewHolder(binding.root)
GridItem用のAdapter
class GridItemAdapter (private val viewModel: MainViewModel) : RecyclerView.Adapter<ItemGridViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemGridViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ItemListGridItemBinding.inflate(layoutInflater, parent, false)
return ItemGridViewHolder(binding)
}
override fun onBindViewHolder(holder: ItemGridViewHolder, position: Int) {
holder.binding.image.load(viewModel.repoItems[position].owner.avatar_url)
}
override fun getItemCount(): Int {
return viewModel.repoItems.size
}
}
class ItemGridViewHolder(val binding: ItemListGridItemBinding) : RecyclerView.ViewHolder(binding.root)
この2つをMergeAdapterにマージする
val bannerAdapter = BannerAdapter()
val gridItemAdapter = GridItemAdapter(viewModel)
val mergeAdapter = MergeAdapter(
bannerAdapter,
gridItemAdapter
)
binding.recyclerView.apply {
val gridLayoutManager = GridLayoutManager(context,3)
gridLayoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
override fun getSpanSize(position: Int): Int = if (position == 0) 3 else 1
}
layoutManager = gridLayoutManager
adapter = mergeAdapter
}
注意点として、LayoutManagerはGridLayoutManagerを使うので、バナーのように1列に表示する場合はSpanSizeの制御をきちんと入れましょう。
あとがき
簡単な実装で、性質の違うデータ、レイアウトを別々のAdapterで管理できるのはとてもよいですね!
地味にいろんなところで使えそうなので、安定版のリリースが待ち遠しいです。