- 使用自定義ItemDecoration 來實現(xiàn)RecyclerView的分組頭,還有吸頂?shù)男Ч?/li>

itemDecoration.gif
1.了解 RecyclerView.ItemDecoration
1.onDraw方法
-
我們看源碼的注釋,看看onDraw方法做什么。
onDraw.png -
看不懂沒關系,翻譯上,大概意思就是這個方法繪制的東西,會在RecyclerView繪制之前繪制,所以會被壓在下面。
翻譯.png
2.onDrawOver方法
-
onDrawOver跟onDraw剛好相反,他是在RecyclerView繪制之后繪制,會蓋在RecyclerView上面。
onDrawOver.png
翻譯.png
3.getItemOffsets方法
- getItemOffsets方法,主要就是給itemView設置偏移量,比如RecyclerView的設置LinearLayoutManger,使用Vertical垂直方向,那上下item之間的分隔線的空間,就可以在這設置。
- outRect.set(0, 5, 0, 0),就代表top方向偏移5像素點,然后就預留出了5像素點高度的空間,給你繪制分隔線,而不會影響itemView。
- 看注釋,設置偏移量要在后面,就是super要么不寫,要么寫在前面,看源碼super把全部設置為0。
- 注釋也告訴你,RecyclerView#getChildAdapterPosition(View)可以通過view獲取position。
-
查看源碼,RecyclerView的LayoutParams,是有viewHolder的,所以可以通過View 獲取LayoutParams,再拿到ViewHolder。
getItemOffsets
image.png
image.png
2.實現(xiàn)分組吸頂效果
1.重寫getItemOffsets方法
- 如果是分組數(shù)據(jù)的頭部,那頭部itemView就設置一個50dp的偏移量來繪制分組頭。
- 如果不是分組頭,itemView就設置一個5像素的偏移量來繪制分隔線。
/**
* 設置itemView偏移大小
*/
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State
) {
super.getItemOffsets(outRect, view, parent, state)
if (parent.adapter is UserAdapter) {
val adapter = parent.adapter as UserAdapter
//RecyclerView的LayoutParams,是有viewHolder的,所以可以通過View 獲取LayoutParams,再拿到ViewHolder
//獲取當前view對應的position
val position = parent.getChildAdapterPosition(view)
//判斷分組頭
if (adapter.isGroupHead(position)) {
outRect.set(0, headHeight, 0, 0)
}
//分隔線
else {
outRect.set(0, 5, 0, 0)
}
}
}
-
RecyclerView這里設置了黃色背景色,這些間隔就是偏移產(chǎn)生的
image.png
2.繪制分組頭跟分隔線
- 我們通過遍歷所有子view,判斷是分組頭,就繪制矩形大小填充偏移的位置,還有繪制標題字體。
- 如果不是頭部只要繪制簡單的矩形就行了,跟自定義view的onDraw差不多的流程。
- 這里用onDraw或者onDrawOver都能實現(xiàn)。
/**
* onDraw先繪制,然后在輪到item,最后是onDrawOver
* 繪制分組的頭部
*/
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDraw(c, parent, state)
if (parent.adapter is UserAdapter) {
val adapter = parent.adapter as UserAdapter
val count = parent.childCount
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight
//遍歷所有子view
for (i in 0 until count) {
val view = parent.getChildAt(i)
val childPosition = parent.getChildAdapterPosition(view)
//在paddingTop范圍內(nèi)繪制
if (view.top - headHeight > parent.paddingTop) {
//如果是分組的頭部
if (adapter.isGroupHead(childPosition)) {
val groupName = adapter.getGroupName(childPosition)
//繪制頭部的背景
val rect = Rect(left, view.top - headHeight, right, view.top)
c.drawRect(rect, headPaint)
//繪制頭部文字
headTextPaint.getTextBounds(groupName, 0, groupName.length, headTextRect)
c.drawText(
groupName,
(left + headTextPadding).toFloat(),
(view.top - (headHeight - headTextRect.height()) / 2).toFloat(),
headTextPaint
)
}
//如果不是頭部,就繪制分隔線
else {
val rect = Rect(left, view.top - 5, right, view.top)
c.drawRect(rect, mPaint)
}
}
}
}
}
3.繪制吸頂效果
- 吸頂效果一個蓋在最上面。
- 第一個可見的itemView如果是分組頭,那繪制的高度要隨著上滑變動,否則直接以最大高度繪制。
- 因為RecyclerView有可能設置padding,所以要考慮繪制時,內(nèi)容跑到padding的區(qū)域,用clip裁剪掉。
/**
* 繪制吸頂效果
*/
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
if (parent.adapter is UserAdapter) {
val adapter = parent.adapter as UserAdapter
val layoutManager = parent.layoutManager
//只考慮LinearLayoutManager
if (layoutManager is LinearLayoutManager) {
//找到RecyclerView第一個顯示的view的position
val position = layoutManager.findFirstVisibleItemPosition()
//通過viewHolder獲取itemView
val childView = parent.findViewHolderForAdapterPosition(position)?.itemView
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight
val top = parent.paddingTop
childView?.let {
//如果第一個可見itemView的下一個是組的頭部,就把吸頂?shù)捻斏先? if (adapter.isGroupHead(position + 1)) {
//繪制吸頂頭部的背景,bottom會隨著上滑越來越小
val bottom = Math.min(topHeight, childView.bottom - top)
val rect = Rect(left, top, right, top + bottom)
c.drawRect(rect, topPaint)
//繪制吸頂?shù)念^部文字
val groupName = adapter.getGroupName(position)
topTextPaint.getTextBounds(groupName, 0, groupName.length, topTextRect)
//將超出的擋住裁掉
val clipRect = Rect(left, top + bottom, right, top)
c.clipRect(clipRect)
c.drawText(
groupName,
(left + topTextPadding).toFloat(),
(top + bottom - (topHeight - topTextRect.height()) / 2).toFloat(),
topTextPaint
)
}
//如果第一個可見itemView的下一個不是組的頭部,就直接繪制吸頂頭部
else {
//繪制吸頂頭部的背景
val rect = Rect(left, top, right, top + topHeight)
c.drawRect(rect, topPaint)
//繪制吸頂?shù)念^部文字
val groupName = adapter.getGroupName(position)
topTextPaint.getTextBounds(groupName, 0, groupName.length, topTextRect)
c.drawText(
groupName,
(left + topTextPadding).toFloat(),
(top + topHeight - (topHeight - topTextRect.height()) / 2).toFloat(),
topTextPaint
)
}
}
}
}
}
-
最終得到這樣的效果,黃色是RecyclerView的背景色,藍色是吸頂?shù)膮^(qū)域,綠色是分組頭。
image.png








