美文网首页Android进阶之路
Android 自定义ItemDecoration-实现分组吸顶

Android 自定义ItemDecoration-实现分组吸顶

作者: 进击的包籽 | 来源:发表于2020-09-23 15:11 被阅读0次

    Github源码地址
    码云源码地址

    • 使用自定义ItemDecoration 来实现RecyclerView的分组头,还有吸顶的效果
    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.实现分组吸顶效果

    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这里设置了黄色背景色,这些间隔就是偏移产生的


      image.png

    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的背景色,蓝色是吸顶的区域,绿色是分组头


      image.png

    相关文章

      网友评论

        本文标题:Android 自定义ItemDecoration-实现分组吸顶

        本文链接:https://www.haomeiwen.com/subject/ponwyktx.html