如何在 RecyclerView 加入分類

開工前準備

本篇使用了第三方的 library - SectionedRecyclerViewAdapter 來協助我們加速建立 RecyclerView Section,別人都幫我們寫好了,還有世界各國的貢獻者幫忙抓 bug,不拿來用實在太可惜啦 XD。

我們使用 Android Studio 開發,開始前先建立一個 Android project。範例程式以 Kotlin 撰寫,使用 Java 開發的同學,可以先了解用法,再到文末 Github 查看完整的程式碼與目錄結構。

匯入 Library

在專案 Gradle 裡匯入 library,再按下 Sync Now:

implementation 'io.github.luizgrp.sectionedrecyclerviewadapter:sectionedrecyclerviewadapter:3.1.0'

*此 library 3.1.0 版僅支援 Androidx,使用 android.support 的同學請匯入 1.2.0 版。

XML 資源檔與 Adapter

在 activity_main.xml 加入一個 RecyclerView:

<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recyclerView"
    android:layout_margin="10dp"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

新增標題和內容要用到的 XML 資源檔

header.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:background="@color/colorPrimary"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/headerText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="20sp"
        android:layout_gravity="center"
        tools:text="標題" />
</LinearLayout>

item.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_margin="10dp"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/itemText"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:textSize="15sp"
        tools:text="內容" />
</LinearLayout>

接著新增 Adapter。

SectionAdapter.kt

class SectionAdapter(
    private val context: Context,
    private val header: String,
    private val item: Array<String>
) :
    Section(
        SectionParameters.builder()
            .itemResourceId(R.layout.item)
            .headerResourceId(R.layout.header)
            .build()
    ) {
    override fun getContentItemsTotal(): Int {
        return item.size
    }

    override fun getHeaderViewHolder(view: View): ViewHolder {
        return HeaderViewHolder(view)
    }

    override fun getItemViewHolder(view: View): ViewHolder {
        return ItemViewHolder(view)
    }

    override fun onBindHeaderViewHolder(holder: ViewHolder) {
        val headerHolder = holder as HeaderViewHolder
        headerHolder.headerText.text = header
    }

    override fun onBindItemViewHolder(holder: ViewHolder, position: Int) {
        val itemHolder = holder as ItemViewHolder
        itemHolder.itemText.text = item[position]
        itemHolder.itemText.setOnClickListener {
            Toast.makeText(context, "正在點擊: $header${item[position]}", Toast.LENGTH_SHORT).show()
        }
    }

    internal inner class HeaderViewHolder(itemView: View) : ViewHolder(itemView) {
        val headerText: TextView = itemView.headerText
    }

    internal inner class ItemViewHolder(itemView: View) : ViewHolder(itemView) {
        val itemText: TextView = itemView.itemText
    }
}

我們將來可以在 onBindHeaderViewHolder 方法控制標題的行為;在 onBindItemViewHolder 方法控制第 position 內容的行為。

MainActivity 要做什麼?

在 MainActivity,我們先宣告此次範例要放進 RecyclerView 的標題與內容:

private val header1 = "A"
private val header2 = "B"
private val header3 = "C"
private val item1: Array<String> = arrayOf("001", "002", "003", "004", "005", "006")
private val item2: Array<String> = arrayOf("007", "008", "009", "010", "011", "012")
private val item3: Array<String> = arrayOf("013", "014", "015", "016", "017", "018")
private val ITEM_PER_LINE: Int = 4

接著建立 SectionedRecyclerViewAdapter 物件,新增三個 SectionAdapter:

val sectionAdapter = SectionedRecyclerViewAdapter()
sectionAdapter.addSection(SectionAdapter(this, header1, item1))
sectionAdapter.addSection(SectionAdapter(this, header2, item2))
sectionAdapter.addSection(SectionAdapter(this, header3, item3))

定義 GridLayout 顯示方式,標題佔一列,內容每列四行:

val glm = GridLayoutManager(this, ITEM_PER_LINE)
glm.spanSizeLookup = object : SpanSizeLookup() {
    override fun getSpanSize(position: Int): Int {
        return if (sectionAdapter.getSectionItemViewType(position) == SectionedRecyclerViewAdapter.VIEW_TYPE_HEADER) {
            ITEM_PER_LINE
        } else {
            1
        }
    }
}

設定 Adpater:

recyclerView.layoutManager = glm
recyclerView.adapter = sectionAdapter

做完了?沒錯,就是這麼簡單

趕快執行看看吧!是不是很簡單呢。

SectionedRecyclerViewKotlinDemo

接著,只要在 MainActivity 設定你想顯示的標題和內容、顯示佈局,改寫 onBindHeaderViewHolderonBindItemViewHolder 的行為,就可以自訂你的 RecyclerView Section了。

完整程式碼

參考資料

作者:Ray

聯騰唯一知道全家咖啡可以三顆豆,但是不常喝咖啡的工程師。

本文由 INFOLINK 聯騰資訊股份有限公司提供,聯騰資訊專注於為零售與餐飲產業提供智慧化的系統解決方案,以 ERP 為核心為客戶開拓 E 化應用,與 POS、BI、EC 等應用實現無縫整合,我們在此分享我們對產業與技術的觀點,歡迎與我們交流或追蹤我們。