RecyclerView中ItemDecoration的精妙用法,实现自定义分隔线、边距和背景效果

2024年 5月 11日 82.6k 0

ItemDecoration 是 RecyclerView 组件的一个非常有用的功能,用于添加自定义的装饰项(如分隔线、边距、背景等)到 RecyclerView 的每个 item 之间或周围。

recyclerView.addItemDecoration()

ItemDecoration主要的三个方法:

  • onDraw(Canvas c, RecyclerView parent, RecyclerView.State state): 在 RecyclerView 的 canvas 上绘制自定义的装饰项,通常用于绘制分隔线或背景。
  • onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state): 与 onDraw 类似,但绘制的内容会出现在 item 的视图之上。在 item 视图上方绘制内容(如高亮或选择效果),可以使用这个方法。
  • getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state): 设置 item 的边距。outRect 参数是一个 Rect 对象,可以设置它的 left、top、right 和 bottom 属性来定义 item 的额外空间。这些额外的空间会用于绘制分隔线或边距。
  • RecyclerView中ItemDecoration的精妙用法,实现自定义分隔线、边距和背景效果-1图片

    • 图1:代表了getItemOffsets(),可以实现类似padding的效果。
    • 图2:代表了onDraw(),可以实现类似绘制背景的效果,内容在上面。
    • 图3:代表了onDrawOver(),可以绘制在内容的上面,覆盖内容。

    分割线

    实现分割线效果需要 getItemOffsets()和 onDraw()2个方法,首先用 getItemOffsets给item下方空出一定高度的空间(例子中是1dp),然后用onDraw绘制这个空间。

    public class SimpleDividerDecoration extends RecyclerView.ItemDecoration {
    
        private int dividerHeight;
        private Paint dividerPaint;
    
        public SimpleDividerDecoration(Context context) {
            dividerPaint = new Paint();
            dividerPaint.setColor(context.getResources().getColor(R.color.colorAccent));
            dividerHeight = context.getResources().getDimensionPixelSize(R.dimen.divider_height);
        }
    
    
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            outRect.bottom = dividerHeight;
        }
    
        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            int childCount = parent.getChildCount();
            int left = parent.getPaddingLeft();
            int right = parent.getWidth() - parent.getPaddingRight();
    
            for (int i = 0; i < childCount - 1; i++) {
                View view = parent.getChildAt(i);
                float top = view.getBottom();
                float bottom = view.getBottom() + dividerHeight;
                c.drawRect(left, top, right, bottom, dividerPaint);
            }
        }
    }

    RecyclerView中ItemDecoration的精妙用法,实现自定义分隔线、边距和背景效果-2图片

    标签

    标签都是覆盖在内容之上的,可以用onDrawOver()来实现,这里简单实现一个颜色标签。

    public class LeftAndRightTagDecoration extends RecyclerView.ItemDecoration {
        private int tagWidth;
        private Paint leftPaint;
        private Paint rightPaint;
    
        public LeftAndRightTagDecoration(Context context) {
            leftPaint = new Paint();
            leftPaint.setColor(context.getResources().getColor(R.color.colorAccent));
            rightPaint = new Paint();
            rightPaint.setColor(context.getResources().getColor(R.color.colorPrimary));
            tagWidth = context.getResources().getDimensionPixelSize(R.dimen.tag_width);
        }
    
        @Override
        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDrawOver(c, parent, state);
            int childCount = parent.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View child = parent.getChildAt(i);
                int pos = parent.getChildAdapterPosition(child);
                boolean isLeft = pos % 2 == 0;
                if (isLeft) {
                    float left = child.getLeft();
                    float right = left + tagWidth;
                    float top = child.getTop();
                    float bottom = child.getBottom();
                    c.drawRect(left, top, right, bottom, leftPaint);
                } else {
                    float right = child.getRight();
                    float left = right - tagWidth;
                    float top = child.getTop();
                    float bottom = child.getBottom();
                    c.drawRect(left, top, right, bottom, rightPaint);
                }
            }
        }
    }

    RecyclerView中ItemDecoration的精妙用法,实现自定义分隔线、边距和背景效果-3图片

    ItemDecoration组合

    ItemDecoration是可以叠加的,可以将多个效果通过addItemDecoration方法叠加,将上面两种效果叠加。

    recyclerView.addItemDecoration(new LeftAndRightTagDecoration(this));
    recyclerView.addItemDecoration(new SimpleDividerDecoration(this));

    RecyclerView中ItemDecoration的精妙用法,实现自定义分隔线、边距和背景效果-4图片

    Section分组

    定义接口用来进行数据分组和获取首字母,重写getItemOffsets()和onDraw()方法,并根据数据进行分组处理。

    public interface DecorationCallback {
    
            long getGroupId(int position);
    
            String getGroupFirstLine(int position);
        }
    public class SectionDecoration extends RecyclerView.ItemDecoration {
        private static final String TAG = "SectionDecoration";
    
        private DecorationCallback callback;
        private TextPaint textPaint;
        private Paint paint;
        private int topGap;
        private Paint.FontMetrics fontMetrics;
    
    
        public SectionDecoration(Context context, DecorationCallback decorationCallback) {
            Resources res = context.getResources();
            this.callback = decorationCallback;
    
            paint = new Paint();
            paint.setColor(res.getColor(R.color.colorAccent));
    
            textPaint = new TextPaint();
            textPaint.setTypeface(Typeface.DEFAULT_BOLD);
            textPaint.setAntiAlias(true);
            textPaint.setTextSize(80);
            textPaint.setColor(Color.BLACK);
            textPaint.getFontMetrics(fontMetrics);
            textPaint.setTextAlign(Paint.Align.LEFT);
            fontMetrics = new Paint.FontMetrics();
            topGap = res.getDimensionPixelSize(R.dimen.sectioned_top);//32dp
    
    
        }
    
    
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            int pos = parent.getChildAdapterPosition(view);
            Log.i(TAG, "getItemOffsets:" + pos);
            long groupId = callback.getGroupId(pos);
            if (groupId < 0) return;
            if (pos == 0 || isFirstInGroup(pos)) {//同组的第一个才添加padding
                outRect.top = topGap;
            } else {
                outRect.top = 0;
            }
        }
    
        @Override
        public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDraw(c, parent, state);
            int left = parent.getPaddingLeft();
            int right = parent.getWidth() - parent.getPaddingRight();
            int childCount = parent.getChildCount();
            for (int i = 0; i < childCount; i++) {
                View view = parent.getChildAt(i);
                int position = parent.getChildAdapterPosition(view);
                long groupId = callback.getGroupId(position);
                if (groupId < 0) return;
                String textLine = callback.getGroupFirstLine(position).toUpperCase();
                if (position == 0 || isFirstInGroup(position)) {
                    float top = view.getTop() - topGap;
                    float bottom = view.getTop();
                    c.drawRect(left, top, right, bottom, paint);//绘制红色矩形
                    c.drawText(textLine, left, bottom, textPaint);//绘制文本
                }
            }
        }
    
        
        private boolean isFirstInGroup(int pos) {
            if (pos == 0) {
                return true;
            } else {
                long prevGroupId = callback.getGroupId(pos - 1);
                long groupId = callback.getGroupId(pos);
                return prevGroupId != groupId;
            }
        }
    
        public interface DecorationCallback {
    
            long getGroupId(int position);
    
            String getGroupFirstLine(int position);
        }
    }
    recyclerView.addItemDecoration(new SectionDecoration(this, new SectionDecoration.DecorationCallback() {
        @Override
        public long getGroupId(int position) {
            return Character.toUpperCase(dataList.get(position).getName().charAt(0));
        }
    
        @Override
        public String getGroupFirstLine(int position) {
            return dataList.get(position).getName().substring(0, 1).toUpperCase();
        }
    }));

    StickyHeader

    头部吸顶效果,header不动肯定是要绘制item内容之上,需要重写onDrawOver()方法,其和Section实现一样。

    public class PinnedSectionDecoration extends RecyclerView.ItemDecoration {
        private static final String TAG = "PinnedSectionDecoration";
    
        private DecorationCallback callback;
        private TextPaint textPaint;
        private Paint paint;
        private int topGap;
        private Paint.FontMetrics fontMetrics;
    
    
        public PinnedSectionDecoration(Context context, DecorationCallback decorationCallback) {
            Resources res = context.getResources();
            this.callback = decorationCallback;
    
            paint = new Paint();
            paint.setColor(res.getColor(R.color.colorAccent));
    
            textPaint = new TextPaint();
            textPaint.setTypeface(Typeface.DEFAULT_BOLD);
            textPaint.setAntiAlias(true);
            textPaint.setTextSize(80);
            textPaint.setColor(Color.BLACK);
            textPaint.getFontMetrics(fontMetrics);
            textPaint.setTextAlign(Paint.Align.LEFT);
            fontMetrics = new Paint.FontMetrics();
            topGap = res.getDimensionPixelSize(R.dimen.sectioned_top);
    
    
        }
    
    
        @Override
        public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
            super.getItemOffsets(outRect, view, parent, state);
            int pos = parent.getChildAdapterPosition(view);
            long groupId = callback.getGroupId(pos);
            if (groupId < 0) return;
            if (pos == 0 || isFirstInGroup(pos)) {
                outRect.top = topGap;
            } else {
                outRect.top = 0;
            }
        }
    
    
        @Override
        public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
            super.onDrawOver(c, parent, state);
            int itemCount = state.getItemCount();
            int childCount = parent.getChildCount();
            int left = parent.getPaddingLeft();
            int right = parent.getWidth() - parent.getPaddingRight();
            float lineHeight = textPaint.getTextSize() + fontMetrics.descent;
    
            long preGroupId, groupId = -1;
            for (int i = 0; i < childCount; i++) {
                View view = parent.getChildAt(i);
                int position = parent.getChildAdapterPosition(view);
    
                preGroupId = groupId;
                groupId = callback.getGroupId(position);
                if (groupId < 0 || groupId == preGroupId) continue;
    
                String textLine = callback.getGroupFirstLine(position).toUpperCase();
                if (TextUtils.isEmpty(textLine)) continue;
    
                int viewBottom = view.getBottom();
                float textY = Math.max(topGap, view.getTop());
                if (position + 1 < itemCount) { //下一个和当前不一样移动当前
                    long nextGroupId = callback.getGroupId(position + 1);
                    if (nextGroupId != groupId && viewBottom < textY ) {//组内最后一个view进入了header
                        textY = viewBottom;
                    }
                }
                c.drawRect(left, textY - topGap, right, textY, paint);
                c.drawText(textLine, left, textY, textPaint);
            }
        }
    }

    RecyclerView中ItemDecoration的精妙用法,实现自定义分隔线、边距和背景效果-5图片

    相关文章

    JavaScript2024新功能:Object.groupBy、正则表达式v标志
    PHP trim 函数对多字节字符的使用和限制
    新函数 json_validate() 、randomizer 类扩展…20 个PHP 8.3 新特性全面解析
    使用HTMX为WordPress增效:如何在不使用复杂框架的情况下增强平台功能
    为React 19做准备:WordPress 6.6用户指南
    如何删除WordPress中的所有评论

    发布评论