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.