Android 自定義ItemDecoration-實現(xiàn)分組吸頂效果

Github源碼地址
碼云源碼地址

  • 使用自定義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
最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容