[kotlin]帶分類的RecyclerView通用實現新思路

Loren1994發表於2018-06-21

UniversalAdapter

這是封裝的帶分類Recyclerview的Adapter,主要解決專案中分類列表繁瑣的重複勞動。

  • 不影響Recyclerview或其他封裝的上拉下拉Recyclerview的正常使用
  • 只需寫一個繼承UniversalAdapter的adapter
  • 支援分類和子項部分的自定義佈局
  • 資料類需實現OnTypeList介面
資料結構JSON示例
[
    {
        "title":"分類1",
        "child_list":[
            {
                "content":"子項1"
            }
        ]
    },
    {
        "title":"分類2",
        "child_list":[
            {
                "content":"子項2"
            }
        ]
    }
]
複製程式碼
效果圖
  • 豎向多列

[kotlin]帶分類的RecyclerView通用實現新思路

  • 豎向單列

[kotlin]帶分類的RecyclerView通用實現新思路

  • 橫向多列

[kotlin]帶分類的RecyclerView通用實現新思路

基本思路

參照以上資料結構示例,陣列巢狀陣列,每個分類對應一個標題和一個List。拿到資料來源後需要區分出哪個欄位是標題,哪個欄位是子項的列表,以便後續的計算和分類。

getItemCount返回每一個分類和標題數量的總和,即將標題和子項當做一個同一級(沒有巢狀)的列表,標題欄獨佔一個position。但資料來源仍然是一個巢狀的列表,最終在onBindViewHolder回撥的時候會計算並分別回撥外層、內層的position。

考慮到ItemDecoration很難支援多列並且只能通過加高child去畫出簡單的標題欄,所以標題欄是根據不同的viewType去畫不同layout的ViewHolder,並通過layoutManager.spanSizeLookup設定為spanCount,以此實現支援多列。

主要難點在於根據onBindViewHolder中的position去計算判斷是子項還是標題項。

關鍵程式碼

  • 資料類要實現的介面
interface OnTypeList<T> {
    fun getTitle(): String
    fun getBody(): MutableList<T>
}
複製程式碼
  • 資料類示例
data class ParentListItem(val childList: MutableList<ChildListItem>,
                          val titleStr: String = "") : OnTypeList<ChildListItem> {
    override fun getBody() = childList

    override fun getTitle() = titleStr
}
複製程式碼

資料類需要實現OnTypeList介面,以便UniversalAdapter知道childList和title對應的欄位。這裡直接返回相應欄位即可。

  • 在UniversalAdapter一初始化便要對標題position記錄,這裡用hashMap去記錄標題的index
private var titleIndexMap = hashMapOf<String, Int>()

init {
   init()
}

private fun init() {
        data.forEachIndexed { index, item ->
            var pos = 0
            repeat(index) {
                pos += data[it].getBody().size
            }
            pos += index
            titleIndexMap[item.getTitle()] = pos
        }
}
複製程式碼

通過資料類實現的介面去獲取childList和title。利用兩層迴圈把陣列每一項的標題和對應的子項數量相加,得到每一個標題的position

  • 計算position的外層和內層index
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        var parentIndex = 0
        var childIndex = position + 1
        for (item in data) {
            if (childIndex > (item.getBody().size + 1)) {
                childIndex -= (item.getBody().size + 1)
                parentIndex++
            } else {
                break
            }
        }
        childIndex -= 2
        if (childIndex == -1) {
            onTitleBindItemView(holder, parentIndex)
        } else {
            onBodyBindItemView(holder, parentIndex, childIndex)
        }
}
複製程式碼

根據當前position,從陣列開頭逐個減去childList.size+1(標題),大於0則parentIndex+1,小於或等於0則說明正處於陣列的當前index中,以此得到外層index,即parentIndex。減的剛好大於0時的position減去1(標題)就是內層子項的index,即childIndex。

  • 追加資料後需要呼叫此方法,以便重新計算標題位置
mAdapter.refresh()
複製程式碼
  • 支援多列
//橫向
//layoutManager.orientation = GridLayoutManager.HORIZONTAL
layoutManager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
      override fun getSpanSize(position: Int): Int {
            return if (mAdapter.isTitle(position)) layoutManager.spanCount else 1
      }
 }
複製程式碼

標題需要佔滿,所以此程式碼是必需的。可根據需求自行在Activity中定製LayoutManager,也可直接使用sample中的UniversalRecyclerview。

專案地址:https://github.com/Loren1994/UniversalTypeRecyclerView

部落格地址:https://loren1994.github.io

如果對你有幫助,請給個star~ 歡迎issue~

相關文章