Sticky Header RecyclerView

James Lin
6 min readAug 6, 2018

--

之前看到有人寫出 Sticky Header 的效果,我就想說來試試看,中途發生了不少問題XD

一開始在思索一個問題,什麼樣的資料結構符合 Group,一個 Title 會對應一堆 Item,那如果要加上排序(像電話簿那樣)的話呢?

借助工作室裡面的人幫忙,於是此結構就誕生了

val data: TreeMap<String, TreeSet<String>>

接著 RecyclerView 在 Adapter 中建造的方式是傳入 position 的,傳入的東西是 0 1 2 3 ....

那麼這個資料結構要怎麼對應呢?

資料結構:

那就有另一種想法:

顯示的資料是要這樣

第一個 Title 中,Item 的 TreeSet 的 Size 是 3,Item3 的 Index 加上 Title 剛好是 3。

可以推測出:當 Adapter 傳入的 position 扣掉前面元素的個數(也就是)的 index 是 0 的時候,目前的元素是 Title,如果不是 Title 則用計算的方式計算要移動幾個 position(類似 Cursor)。

當程式在跑 TreeSet 的時候,會計算要跑幾次,底下 for 迴圈粗斜體的部分,直到 position 是 0 的時候,表示找到元素了,如果不是 0,就表示該 position 不在此 TreeSet 中。

private fun find(key: String, items: TreeSet<String>, _pos: Int): String? {
var pos = _pos
if (pos == 0) {
return key
}
pos--
for (item: String in items) {
if (pos == 0) {
return item
}
pos--
}

return null
}

於是需要找下一個 TreeSet,當 find 方法回傳 null 代表在此 TreeSet 找不到元素,那麼就去除此元素 pos -= itemSet.size + 1,position

override fun getItem(_pos: Int): String {
var pos = _pos
for (key in data.keys) {
var itemSet = data.get(key)!!
var item = find(key, itemSet, pos)
if (item != null) {
return item
}
pos -= itemSet.size + 1
}
throw RuntimeException("Can't find concreteData")
}

接著要來解決 View 的問題

首先,要先定義什麼東西在滑動的時候不會變、什麼東西會變。

圖一

在圖一中,Text Padding,在滑動的過程中是不變的。

所以在一開始畫 Title 的時候,就要將不會變動的值存起來。

bottomPadding = currentTitleView.measuredHeight - currentTitleView.titleTextView.bottom

if (holder is VerticalAdapter.TitleHolder) {
var titleHolder = holder as VerticalAdapter.TitleHolder
//solve problem in adapter
titleData.put(titleHolder.textView.text.toString(),
titleHolder.itemView.height)

val measureWidth =View.MeasureSpec.makeMeasureSpec(
titleHolder.itemView.width, View.MeasureSpec.EXACTLY)
val measuredHeight = View.MeasureSpec.makeMeasureSpec(
titleHolder.itemView.height, View.MeasureSpec.EXACTLY)

textHeight = currentView!!.titleTextView.measuredHeight

bottomPadding = currentTitleView.measuredHeight
- currentTitleView.titleTextView.bottom

currentTitleView.measure(measureWidth, measuredHeight)
currentTitleView.layout(
0,
0,
titleHolder.itemView.width,
titleHolder.itemView.height)
currentTitleView.draw(c!!)

解釋 titleData 在做什麼事,他是負責從 Adapter 去取得 titleView 的高度,以便在 ItemDecoration 在畫 Title 的時候能畫出正確的 Title Height

核心概念:當下一個 Title 碰到目前的 Title 的時候,目前的 Title 需要往上滑動,目前 Title 的 Height 是會隨著滑動而改變的。

var titleBottom = Math.min(nextHolder!!.itemView.top, viewHeigh)

快速滑動造成 的 bug 還須研究研究~~

這篇文章只介紹了核心的概念,實作的 code 放在 GitHub。

GitHub

--

--

James Lin
James Lin

No responses yet