Github source address

Code cloud source address

  • Use custom ItemDecoration to achieve RecyclerView group head, and the effect of the top.

1. Understand the RecyclerView. ItemDecoration

1. The ontouch method

  • Let’s look at the source code comments to see what the onDraw method does.

  • This method draws things before RecyclerView draws them, so they get squeezed underneath.

2. OnDrawOver method

  • OnDrawOver is the opposite of onDraw, it is drawn after RecyclerView, it will be over RecyclerView.

3. GetItemOffsets method

  • The getItemOffsets method is used to set the offset of the itemView, such as the RecyclerView LinearLayoutManger. Use Vertical to set the space between the upper and lower items.
  • Outrect. set(0, 5, 0, 0), which offsets the top direction by 5 pixels, then gives you 5 pixels of space to draw the dividing line without affecting the itemView.
  • So if you look at the comments, you want to set the offset behind, so super is either not going to be there, or it’s going to be in front, so if you look at the source code, super is going to set everything to zero.
  • The comment also tells you that RecyclerView#getChildAdapterPosition(View) can get position from View.
  • View source code, RecyclerView LayoutParams, is viewHolder, so you can get LayoutParams through View, and then get viewHolder.

2. Achieve the effect of grouping top suction

1. Override the getItemOffsets method

  • If it is the header of the group, the header itemView sets an offset of 50dp to draw the group header.
  • If it is not a grouping header, the itemView sets an offset of 5 pixels to draw the dividing line.
/** * Sets itemView offset */
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 has a viewHolder, so you can use View to get LayoutParams, and then get viewHolder
        // Get the position corresponding to the current view
        val position = parent.getChildAdapterPosition(view)

        // Determine the group header
        if (adapter.isGroupHead(position)) {
            outRect.set(0, headHeight, 0.0)}/ / separation line
        else {
            outRect.set(0.5.0.0)}}}Copy the code
  • RecyclerView where the yellow background color is set, these intervals are generated by the offset.

2. Draw a group header and dividing line

  • We draw the size of the rectangle to fill in the offset position and draw the title font by iterating through all the sub-views to determine the group header.
  • If it’s not a header, just draw a simple rectangle, similar to the onDraw process for custom views.
  • I can do this either with onDraw or onDrawOver.
/** * onDraw draws first, then onDrawOver draws the head of the group */
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

        // Iterate over all child Views
        for (i in 0 until count) {
            val view = parent.getChildAt(i)
            val childPosition = parent.getChildAdapterPosition(view)

            // Draw in the paddingTop range
            if (view.top - headHeight > parent.paddingTop) {
                // If it is the header of the group
                if (adapter.isGroupHead(childPosition)) {
                    val groupName = adapter.getGroupName(childPosition)

                    // Draw the background of the header
                    val rect = Rect(left, view.top - headHeight, right, view.top)
                    c.drawRect(rect, headPaint)

                    // Draw the header text
                    headTextPaint.getTextBounds(groupName, 0, groupName.length, headTextRect)
                    c.drawText(
                        groupName,
                        (left + headTextPadding).toFloat(),
                        (view.top - (headHeight - headTextRect.height()) / 2).toFloat(),
                        headTextPaint
                    )
                }
                // If it is not a header, draw a separator line
                else {
                    val rect = Rect(left, view.top - 5, right, view.top)
                    c.drawRect(rect, mPaint)
                }
            }
        }
    }
}
Copy the code

3. Draw the top suction effect

  • Top suction effect a cap on top.
  • If the first visible itemView is a group header, the height of the drawing will change with the slide up, otherwise it will be drawn at the maximum height.
  • Because it is possible to set the padding in RecyclerView, it is necessary to consider that when drawing, the content runs to the padding area and clips out with clip.

/** * draw the top drawing effect */
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
        // Just consider the LinearLayoutManager
        if (layoutManager is LinearLayoutManager) {
            // Find the position of the first RecyclerView to display
            val position = layoutManager.findFirstVisibleItemPosition()
            // Get the itemView from viewHolderval childView = parent.findViewHolderForAdapterPosition(position)? .itemView val left = parent.paddingLeft val right = parent.width - parent.paddingRight val top = parent.paddingTop childView? .let {// If the head of the group is next to the first visible itemView, add the top
                if (adapter.isGroupHead(position + 1)) {
                    // Draw the background of the top, the bottom gets smaller and smaller as it slides up
                    val bottom = Math.min(topHeight, childView.bottom - top)
                    val rect = Rect(left, top, right, top + bottom)
                    c.drawRect(rect, topPaint)

                    // Draw the header text for the top
                    val groupName = adapter.getGroupName(position)
                    topTextPaint.getTextBounds(groupName, 0, groupName.length, topTextRect)

                    // Cut off the excess
                    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
                    )
                }
                // If the first visible itemView next is not the head of the group, draw the top head directly
                else {
                    // Draw the background of the suction head
                    val rect = Rect(left, top, right, top + topHeight)
                    c.drawRect(rect, topPaint)

                    // Draw the header text for the top
                    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
                    )
                }
            }
        }
    }
}
Copy the code
  • In the end, yellow is the background color of RecyclerView, blue is the suction top area, and green is the grouping head.