UniversalAdapter
這是封裝的帶分類Recyclerview的Adapter,主要解決專案中分類列表繁瑣的重複勞動。
- 不影響Recyclerview或其他封裝的上拉下拉Recyclerview的正常使用
- 只需寫一個繼承UniversalAdapter的adapter
- 支援分類和子項部分的自定義佈局
- 資料類需實現OnTypeList介面
資料結構JSON示例
[
{
"title":"分類1",
"child_list":[
{
"content":"子項1"
}
]
},
{
"title":"分類2",
"child_list":[
{
"content":"子項2"
}
]
}
]
複製程式碼
效果圖
- 豎向多列
- 豎向單列
- 橫向多列
基本思路
參照以上資料結構示例,陣列巢狀陣列,每個分類對應一個標題和一個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