日々是好日

プログラミングについてのあれこれ、ムダ知識など

StickyListHeaders風のライブラリを自作する-3

StickyListHeaders風のライブラリを自作する-2 - 日々是好日の続き。

PinningListAdapter について考えてみる。

リポジトリ作りました。 github.com

PinningListAdapter

今作っている Sticky なリストビュー用のアダプタ。 データセットの型とか ViewHolder を柔軟にするため抽象クラスで定義している。 コンストラクタでデータセットを渡す是非は考えなければならない…。

abstract class PinningListAdapter<E, VH : RecyclerView.ViewHolder>(private val list: List<E>) :
  RecyclerView.Adapter<VH>(),
  PinningListDecoration.PinningListListener {

  //HEADER の目印となる定数。viewTypeの仕分けに使用
  companion object Constants {
    const val HEADER = 200
  }

  //getItemViewType は具象クラスで実装
  override fun isHeader(adapterPosition: Int): Boolean {
    return getItemViewType(adapterPosition) == HEADER
  }

  //ヘッダーに当たるまでさかのぼり
  override fun getCurrentHeaderPosition(adapterPosition: Int): Int? {
    var index = adapterPosition
    while (index > -1) {
      if (isHeader(index)) return index
      index--
    }
    return null
  }

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

//PinningListListener はこれ
interface PinningListListener {
  val headerLayout: Int?
  fun isHeader(adapterPosition: Int): Boolean
  fun getCurrentHeaderPosition(adapterPosition: Int): Int?
  fun bindHeaderData(header: View, adapterPosition: Int)
}

利用する場合の具象クラスでは次のように実装。 必ず override しなければならないメソッド(headerLayoutのみ変数)は次のとおり。 RecyclerViewのメソッドは普段でもほぼ必ず override するやつですね。

  • RecyclerView#onCreateViewHolder
  • RecyclerView#onBindViewHolder
  • RecyclerView#getItemViewType
  • PinningListListener#bindHeaderData
  • PinningListListener#headerLayout: Int?
    • 一応ヘッダーが無くても使えるように null 許容
class MyPinningListAdapter(private val list: List<Int>, override val headerLayout: Int?)
  : PinningListAdapter<Int, RecyclerView.ViewHolder>(list) {

  override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    val inflater = LayoutInflater.from(parent.context)
    
    //viewType で展開する ViewHolder を仕分け
    return when {
      viewType == HEADER && headerLayout != null -> {...}
      else -> {...}
    }
  }

  override fun onBindViewHolder(holder: RecyclerView.ViewHolder, adapterPosition: Int) {
    //実装した ViewHolder によりバインドの処理を仕分け
    when (holder) {
      is HeaderViewHolder -> {...}
      is ItemViewHolder -> {...}
      else -> {...}
    }
  }

  override fun getItemViewType(adapterPosition: Int): Int {
    //基本は list[adapterPosition] 等によりリストのデータが
    //ヘッダーかどうかを判定し、viewType を返す
    return when (...) {
      0 -> HEADER
      else -> super.getItemViewType(adapterPosition)
    }
  }

  //PinningListDecorationにおいて、ヘッダーだった場合のみ呼ばれるので、
  //View はヘッダー決め打ちでおk
  override fun bindHeaderData(header: View, adapterPosition: Int) {
    header.header_title.text = "HEADER: " + list[adapterPosition].toString()
  }

  //ヘッダー用とアイテム用とで ViewHolder を複数定義
  class ItemViewHolder(view: View): RecyclerView.ViewHolder(view) {...}
  class HeaderViewHolder(view: View): RecyclerView.ViewHolder(view) {...}
}

課題

  • 複数の ViewHolder(上記+フッター等) を定義するとき、viewType での仕分けがどんどん膨らんでしまう
  • ViewHolder にインターフェース実装して、条件分岐を減らしてみたい

RecyclerView ぜんぜんわからん。