Android 自定义ItemDecoration-实现分组吸顶效果
Posted 进击的包籽
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android 自定义ItemDecoration-实现分组吸顶效果相关的知识,希望对你有一定的参考价值。
文章目录
- 使用自定义ItemDecoration 来实现RecyclerView的分组头,还有吸顶的效果。
1.了解 RecyclerView.ItemDecoration
1.onDraw方法
-
我们看源码的注释,看看onDraw方法做什么。
-
看不懂没关系,翻译上,大概意思就是这个方法绘制的东西,会在RecyclerView绘制之前绘制,所以会被压在下面。
2.onDrawOver方法
- onDrawOver跟onDraw刚好相反,他是在RecyclerView绘制之后绘制,会盖在RecyclerView上面。
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。
2.实现分组吸顶效果
1.重写getItemOffsets方法
- 如果是分组数据的头部,那头部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这里设置了黄色背景色,这些间隔就是偏移产生的。
2.绘制分组头跟分隔线
- 我们通过遍历所有子view,判断是分组头,就绘制矩形大小填充偏移的位置,还有绘制标题字体。
- 如果不是头部只要绘制简单的矩形就行了,跟自定义view的onDraw差不多的流程。
- 这里用onDraw或者onDrawOver都能实现。
/**
* 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范围内绘制
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,所以要考虑绘制时,内容跑到padding的区域,用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的下一个是组的头部,就把吸顶的顶上去
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)
//绘制吸顶的头部文字
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)
//绘制吸顶的头部文字
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的背景色,蓝色是吸顶的区域,绿色是分组头。
以上是关于Android 自定义ItemDecoration-实现分组吸顶效果的主要内容,如果未能解决你的问题,请参考以下文章
RecyclerView ItemDecoration 自定义高度和颜色
RecyclerView ItemDecoration 自定义高度和颜色
recycleView自定义ItemDecoration解决列表第一个item和最后一个item的间距难统一问题