search
LoginSignup
29

More than 5 years have passed since last update.

posted at

RecyclerViewのヘッダ、フッタ、セクション

 ヘッダ、フッタ、セクション

RecyclerViewでは、ヘッダ、フッタ、セクションのViewを自前で用意し、設定してあげる必要がある。

ベースはこちら
RecyclerViewの実装

 ゴール

ヘッダ、フッタ、セクションをそれぞれカスタムビュー化し、別レイアウトとする。
ヘッダを列の頭に設定
フッタを列の後ろに設定
セクションは列の値の途中追加

実装

Adapterのデータクラス
RecyclerAdapterでのヘッダ、フッタ、セクション、ボディ部分の判定もtypeにて行う
今回はカスタムビューでも使用する

RecyclerState.kt
class RecyclerState(){

  constructor(type: RecyclerType, text: String): this(){
    this.type = type
    this.text = text
  }

  // RcyclerAdapterにて追加するレコードのタイプ
  var type: RecyclerType = RecyclerType.BODY
  var text: String = ""
}

Type

RecyclerAdapterでの判定で使用するenum

RecyclerType.kt
enum class RecyclerType(val int: Int){
  HEADER(0),
  FOOTER(1),
  SECTION(2),
  BODY(3);

  companion object {
    // Intからenumへの変換
    fun fromInt(int: Int): RecyclerType{
      return values().firstOrNull { it.int == int }
        ?: RecyclerType.BODY
    }
  }
}

 Activity

ヘッダ、ボディ、ボディの間にセクション、フッタの順にテストデータが入るようにする

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

    val states = arrayListOf<RecyclerState>()
    // ヘッダ追加
    val headerState = RecyclerState(RecyclerType.HEADER, "へっだ")
    states.add(headerState)

    var secCounter = 0
    for(i in 1..5){

      // 2 件目 と 3 件目 の上にセクションを追加
      if(i == 2 || i == 3){
        secCounter++
        val sectionState = RecyclerState(RecyclerType.SECTION, "セクション(区切り) No. $secCounter")
        states.add(sectionState)
      }

      val state = RecyclerState(RecyclerType.BODY, "$i 件目")
      states.add(state)
    }

    // フッタ追加
    val footerState = RecyclerState(RecyclerType.FOOTER, "ふった")
    states.add(footerState)

    val adapter = RecyclerAdapter(this, states)

    val recycler = findViewById<RecyclerView>(R.id.mainRecycler)
    recycler.adapter = adapter
  }
}

Adapter

RecyclerViewのアダプタ
タイプを判定してデータ格納

RecyclerAdapter.kt
class RecyclerAdapter(
  private val context: Context,
  private val states: List<RecyclerState>) :
  RecyclerView.Adapter<RecyclerView.ViewHolder>() {

  override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
    when(RecyclerType.fromInt(viewType)){
      RecyclerType.HEADER -> {
        val view = RecyclerItemHeaderView(context)
        return RecyclerItemHeaderViewHolder(view)
      }
      RecyclerType.FOOTER -> {
      val view = RecyclerItemFooterView(context)
      return RecyclerItemFooterViewHolder(view)
    }
      RecyclerType.SECTION -> {
      val view = RecyclerItemSectionView(context)
      return RecyclerItemSectionViewHolder(view)
    }
      RecyclerType.BODY -> {
        val view = RecyclerItemView(context)
        return RecyclerItemViewHolder(view)
      }
    }
  }

  override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
    when(viewHolder){
      is RecyclerItemHeaderViewHolder ->{
        viewHolder.update(states[position])
      }
      is RecyclerItemFooterViewHolder ->{
        viewHolder.update(states[position])
      }
      is RecyclerItemSectionViewHolder ->{
        viewHolder.update(states[position])
      }
      is RecyclerItemViewHolder ->{
        viewHolder.update(states[position])
      }
    }
  }

  override fun getItemViewType(position: Int): Int {
    return states[position].type.int
  }

  override fun getItemCount(): Int {
    return states.count()
  }
}

ViewHolder

レイアウトを操作するクラス

RecyclerItemViewHolder.kt
class RecyclerItemViewHolder(private val view: RecyclerItemView) : RecyclerView.ViewHolder(view) {
  fun update(state: RecyclerState){
    view.update(state)
  }
}

Custom View

今回もヘッダ、フッタ、セクション、ボディ 全てをカスタムビューにしている
セクションだけ色つけたので載せておく

recycler_item_section_view.xml
<?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="50dp"
    android:background="@android:color/darker_gray">

    <TextView
        android:id="@+id/recyclerItemSectionText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:text="セクション"
        android:textColor="@android:color/holo_blue_dark"
        android:textSize="20sp"
        android:textStyle="bold"/>

    <View
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_gravity="bottom"
        android:background="@android:color/darker_gray"/>

</FrameLayout>
RecyclerItemSectionView.kt
class RecyclerItemSectionView constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr){
  private val textView: TextView

  init {
    val rootView = LayoutInflater.from(context).inflate(R.layout.recycler_item_section_view, this)
    textView = rootView.findViewById(R.id.recyclerItemSectionText)
    setOnClickListener {
      // クリック処理
    }
  }

  fun update(state: RecyclerState){
    textView.text = state.text
  }
}

できあがり

スクリーンショット 2017-08-31 19.41.10.png

注意点

getItemViewTypeをoverrideするのを忘れないようにずっと0で小パニックになります。

RecyclerAdapter.kt
  override fun getItemViewType(position: Int): Int {
    return states[position].type.int
  }

今回はstateにAdapterで種類を判定できるtype追加して判定を行ったけど、
Adapter自体にaddFooter()のようなメソッドを追加して、
これ以上データが無いかどうか判定してから、addFooter()を呼び出すのが実務に近いかも。

まとめ

カスタムビューとViewHolderをそれぞれ作るのに時間かかるのが難点
ヘッダ、フッタは1つずつなので、カスタムビュー作らない可能性も考慮してもいいかも。
セクションは使いまわす可能性高いのでカスタムビューが良いかと。

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
What you can do with signing up
29