RecyclerView分组显示demo

先看效果

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值