Android RecyclerView realizes the ceiling dynamic effect, detailed analysis

Link: https://download.csdn.net/download/JasonXu94/87786702
202212220944467

1. Item Decoration


ItemDecoration allows the application to add specific drawings or layout offsets to specific Views, which is very useful for drawing dividing lines between Views, visual grouping boundaries, and so on.

When we call the addItemDecoration()
method to add decoration, RecyclerView will call the onDraw method of this class to draw the dividing line, that is to say: the dividing line is drawn.

RecyclerView.addItemDecoration()

RecyclerView.ItemDecoration, this class is an abstract class, the official currently only provides an implementation class
DividerItemDecoration.

public abstract static class ItemDecoration

public class DividerItemDecoration extends RecyclerView.ItemDecoration

There are 3 methods in it:

onDraw():
Draws any appropriate decorations on the canvas provided to the RecyclerView. Anything drawn via this method will be drawn before the item view is drawn, so will appear below the view.

public void onDraw(Canvas c, RecyclerView parent, State state) {
            onDraw(c, parent);
        }

drawing effect

image-20230516164805173

onDrawOver(): Draws any appropriate decorations on the canvas provided to the RecyclerView. Anything drawn via this method will be drawn after the item view is drawn, and thus will appear above the view.

public void onDrawOver(Canvas c, RecyclerView parent, State state) {
            onDrawOver(c, parent);
        }

drawing effect

image-20230516164822315

getItemOffsets(): Retrieves any offsets for a given item. Each field of outRect specifies how many pixels the item view should be inset, similar to padding or margin. The default implementation sets outRect's bounds to 0 and returns.

public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state) {
            getItemOffsets(outRect, ((LayoutParams)view.getLayoutParams()).getViewLayoutPosition(),
                    parent);
        }

2. Realize the ceiling effect of RecyclerView

1. Implement a simple RecyclerView

The implementation details of the following RecyclerView are omitted.

image-20230516164844439

2. Draw the dividing line through ItemDecoration

Customize ItemDecoration.

Create a fruitDecotion class that inherits ItemDecoration and implements three methods: onDraw, onDrawover, and getItemOffsets.

RecyclerView calls custom fruitDecoration

RecyclerView.addItemDecoration(new fruitDecoration(this))

Draw a dividing line in getItemOffsets. When it is a group name, reserve more space (that is, the height of the dividing line is larger than that of the ordinary dividing line).

@Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        if (parent.getAdapter() instanceof fruitAdapter){
            // parent.getAdapter()获取到当前RecyclerView的Adapter
            fruitAdapter adapter = (fruitAdapter) parent.getAdapter();
            // 获取当前view的位置
            int position = parent.getChildLayoutPosition(view);
            if (adapter.isGroupHeader(position)) {
                // 如果是头部,则预留更大的地方
                outRect.set(0, groupHeaderHeight, 0, 0);
            }else {
                outRect.set(0, 4, 0, 0);
            }
        }

achieve effect

2022122209445211

3. Draw the group name for each group

When it is a group name, draw the group name where the larger dividing line is. Implemented in the onDraw() method

@Override
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDraw(c, parent, state);
        if (parent.getAdapter() instanceof fruitAdapter) {
            fruitAdapter adapter = (fruitAdapter) parent.getAdapter();
            //当前屏幕中item的个数
            int count = parent.getChildCount();
            // 获取当前RecyclerView距离屏幕左边的padding
            int left = parent.getPaddingLeft();
            // right == RecyclerView的宽度 - RecyclerView的右padding
            int right = parent.getWidth() - parent.getPaddingRight();
            for (int i = 0; i < count; i++) {
                //获取对应 i 的 view
                View view = parent.getChildAt(i);
                // 获取 i 的 view 的布局位置
                int position = parent.getChildLayoutPosition(view);
                // 判断是否是头部
                boolean isGroupHeader = adapter.isGroupHeader(position);
                // i 的 view 是头部并且 当前view的top位置距离屏幕的顶部还有距离
                if(isGroupHeader && view.getTop() - groupHeaderHeight - parent.getPaddingTop() >=0) {
                    c.drawRect(left, view.getTop() - groupHeaderHeight, right, view.getTop(), headPaint);
                    String groupName = adapter.getGroupName(position);
                    textPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
                    c.drawText(groupName, left + 20, view.getTop() - groupHeaderHeight / 2f + textRect.height() / 2f, textPaint);
                }else if(view.getTop() - groupHeaderHeight - parent.getPaddingTop() >=0){
                    //分割线
                    c.drawRect(left, view.getTop() - 4, right, view.getTop(), headPaint);
                }
            }
       }
    }

achieve effect

image-20230516164935046

4. Realize the ceiling effect

Because the onDrawOver method is drawn after the itemView is drawn, the ceiling of the group name is written in the onDrawOver method. It should be noted that when our second group name reaches the top, we need to replace the current top group name.

 @Override
    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
        if (parent.getAdapter() instanceof fruitAdapter) {
            fruitAdapter adapter = (fruitAdapter) parent.getAdapter();
            //TODO 返回可见区域内的第一个item的position
            int position =((LinearLayoutManager) parent.getLayoutManager()).findFirstVisibleItemPosition();
            // 获取第一个item的view
            View itemView = parent.findViewHolderForAdapterPosition(position).itemView;
            int left = parent.getPaddingLeft();
            int right = parent.getWidth() - parent.getPaddingRight();
            int top = parent.getPaddingTop();
            //TODO 当第二个是头部时,
            boolean isGroupHeader = adapter.isGroupHeader(position + 1);
            if (isGroupHeader) {
                int bottom = Math.min(groupHeaderHeight, itemView.getBottom() - parent.getPaddingTop());
                c.drawRect(left, top, right, top + bottom, headPaint);
                String groupName = adapter.getGroupName(position);
                textPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
                c.drawText(groupName, left + 20, top + bottom - groupHeaderHeight / 2f
                        + textRect.height() / 2f, textPaint);
            }else { // 如果不是头部, 即普通的itemView,则当前的头部一直固定在顶部
                c.drawRect(left, top, right, top + groupHeaderHeight, headPaint);;
                String groupName = adapter.getGroupName(position);
                textPaint.getTextBounds(groupName, 0, groupName.length(), textRect);
                c.drawText(groupName, left + 20, top + groupHeaderHeight / 2f
                        + textRect.height() / 2f, textPaint);
            }
        }
    }

achieve effect

image-20230516164951564

complete demo

Link: https://download.csdn.net/download/JasonXu94/87786702

This is the end of this article about the process analysis of Android RecyclerView to realize the dynamic effect of ceiling suction

Guess you like

Origin blog.csdn.net/JasonXu94/article/details/130709143