先看效果

1.先讲原理:
实现上图效果主要是对 RecyclerView.ItemDecoration 进行继承分别重写getItemOffsets()、onDraw()、onDrawOver()三个方法来实现。
主体思路:第一步先将每个item的间隔设置开留出空间,第二步将留出的位置绘制上线条或者文字,第三步在视图上在绘制一个view放在最上方用于做下一个分组到了顶部会把上一个分组推走的效果。
1.先要准备item的数据,数据主要分为显示内容,和每一个大分组的类别
2.先利用getItemOffsets()将每个item之间的间隔计算出来,设置item间隔为后边绘制文字和线留出空间来。不提前预留空间后边绘制的东西看不到。
/**
* 1.先把每个item的之间的间距计算出来
* 自定义getItemOffsets函数
* 该函数用于计算每个item的偏移量,以实现特定的布局效果
* 在这个函数中,我们主要关注的是设置每个item的上边距,根据item是否是组头来决定边距的大小
*
* @param outRect 用于记录每个item的偏移量的矩形对象
* @param view 当前正在处理的item视图
* @param parent RecyclerView对象,用于访问适配器和其他RecyclerView相关的信息
* @param state RecyclerView的状态对象,包含有关当前RecyclerView布局状态的信息
*/
override fun getItemOffsets(
outRect: Rect,
view: View,
parent: RecyclerView,
state: RecyclerView.State,
) {
super.getItemOffsets(outRect, view, parent, state)
// 检查当前RecyclerView的适配器是否为MyAdapter类型
if (parent.adapter is MyAdapter) {
// 将适配器转换为MyAdapter类型以便于后续操作
var myadapte: MyAdapter = parent.adapter as MyAdapter
// 获取当前item在适配器中的位置
val childAdapterPosition = parent.getChildAdapterPosition(view)
// 判断当前item是否是组头
val gourpHeader = myadapte.isGourpHeader(childAdapterPosition)
// 根据是否是组头来设置item的上边距
if (gourpHeader) {
outRect.top = headHight
} else {
outRect.top = 5
}
}
}
3.第一步给我设置了间隔空间后,第二部我们就可以在 onDraw() 方法中把上边空出的位置绘制上我们需要的内容
/**
* 2.将第一步计算出来的间隔绘制相依的显示内容
*/
override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
// 调用父类的onDraw方法以处理默认绘制逻辑
super.onDraw(c, parent, state)
// 仅当RecyclerView的适配器是MyAdapter类型时执行自定义绘制逻辑
if (parent.adapter is MyAdapter) {
// 定义绘制区域的左边界和右边界
val Left = parent.paddingLeft
val Right = parent.width - parent.paddingRight
// 设置头部绘制的颜色为绿色
headPaint?.color = Color.rgb(0, 255, 0)
// 强制转换适配器为MyAdapter类型以访问其特定方法
val madapter = parent.adapter as MyAdapter
// 遍历RecyclerView的所有子视图
for (i in 0 until parent.childCount) {
// 获取当前子视图和其在适配器中的位置
val childAtView = parent.getChildAt(i)
val childAdapterPosition = parent.getChildAdapterPosition(childAtView)
// 使用Rect定义裁剪区域
val clipRect = RectF(
Left.toFloat(),
parent.paddingTop.toFloat(),
Right.toFloat(),
parent.height.toFloat()
)
// 从(100,100)到(500,500)的矩形区域
c.clipRect(clipRect)
// 检查当前项是否为组头
if (madapter.isGourpHeader(childAdapterPosition)) {
// 绘制组头背景
c.drawRect(
Left.toFloat(),
(childAtView.top - headHight).toFloat(), Right.toFloat(),
childAtView.top.toFloat(), headPaint!!
)
// 获取文本绘制的基线位置
val fontMetrics: Paint.FontMetrics = textPaint!!.fontMetrics
val baseline: Float =
headHight.toFloat() / 2 + (fontMetrics.descent + fontMetrics.ascent) / 2
// 绘制组名文本
c.drawText(
madapter.getGroupName(childAdapterPosition), Left.toFloat(),
childAtView.top.toFloat() - baseline, textPaint!!
)
} else {
// 绘制非组头项的背景
c.drawRect(
Left.toFloat(),
(childAtView.top - 5).toFloat(), Right.toFloat(),
childAtView.top.toFloat(), headPaint!!
)
}
}
}
}
4.这个时候你已经得到了一个有分割线和有分组标题的recyclerview,接下需要单独的绘制头部,那个可以上移的view,实际上看到顶部被推上去的效果是后期绘制了一个view覆盖上去的,通过覆盖布局向上移动实现了动画中看到的被顶上去的效果。通过 onDrawOver()实现单独绘制一个view出来
/**
* 3.在RecyclerView顶部绘制一块儿view用于达到可以上移缩放的目的。
* 在RecyclerView上绘制额外的装饰性内容。
* 此方法主要用于在RecyclerView及其子视图上绘制额外的图形或文本,以提供更丰富的用户体验。
*
* @param c 用于绘制的画布。
* @param parent RecyclerView实例,用于访问适配器、布局管理器等。
* @param state RecyclerView的当前状态,包含正在显示的条目信息。
*/
override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
super.onDrawOver(c, parent, state)
if (parent.adapter is MyAdapter) {
// 获取RecyclerView的左边、右边和上边的位置
val left = parent.paddingLeft
val right = parent.width - parent.paddingRight
val top = parent.paddingTop
// 将parent.adapter转换为MyAdapter类型,以便调用其特定方法
val myAdapter = parent.adapter as MyAdapter
// 返回可见区域内的第一个item的position
val position =
(parent.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()
// 获取当前项的视图
val itemview = parent.findViewHolderForAdapterPosition(position)?.itemView
// 获取文本画笔的字体指标,用于计算基线位置
val fontMetrics: Paint.FontMetrics = textPaint!!.fontMetrics
// 计算文本基线的位置
val baseline: Float =
headHight.toFloat() / 2 + (fontMetrics.descent + fontMetrics.ascent) / 2
// 判断下一个位置是否是组头
if (myAdapter.isGourpHeader(position + 1)) {
// 计算矩形的底部边界
val bottom = Math.min(
(top + headHight).toFloat(),
itemview?.bottom?.toFloat() ?: (top + headHight).toFloat()
)
// 绘制矩形背景
c.drawRect(
left.toFloat(), top.toFloat(), right.toFloat(), bottom,
headPaint!!
)
// 在矩形内绘制组名
c.drawText(
myAdapter.getGroupName(position), left.toFloat(),
bottom - baseline, textPaint!!
)
} else {
// 如果不是组头,绘制不同的矩形背景
c.drawRect(
left.toFloat(), top.toFloat(), right.toFloat(), (top + headHight).toFloat(),
headPaint!!
)
// 在矩形内绘制组名
c.drawText(
myAdapter.getGroupName(position), left.toFloat(),
top.toFloat() + headHight - baseline, textPaint!!
)
}
}
}
demo地址
https://gitee.com/han-shaoshuai/MyRecyclervView.git
3769

被折叠的 条评论
为什么被折叠?



