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

开发 前端
getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state): 设置 item 的边距。outRect 参数是一个 Rect 对象,可以设置它的 left、top、right 和 bottom 属性来定义 item 的额外空间。这些额外的空间会用于绘制分隔线或边距。

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

recyclerView.addItemDecoration()

ItemDecoration主要的三个方法:

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

图片图片

  • 图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);
        }
    }
}

图片图片

标签

标签都是覆盖在内容之上的,可以用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);
            }
        }
    }
}

图片图片

ItemDecoration组合

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

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

图片图片

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);
        }
    }
}

图片图片

责任编辑:武晓燕 来源: 沐雨花飞蝶
相关推荐

2020-02-21 13:55:35

CSS分隔线前端

2010-08-12 09:45:33

jQuery自定义事件

2010-09-08 09:11:32

CSSmargin

2024-05-30 08:23:37

ViewPager滑动效果接口

2022-07-19 06:20:47

CSSbackground

2013-01-06 10:43:54

Android开发View特效

2021-11-23 15:06:42

Kubernetes 运维开源

2010-08-13 11:34:54

Flex自定义事件

2010-08-25 10:10:30

CSSmargin

2013-03-28 10:58:30

自定义Android界android

2022-06-06 09:01:16

SwiftUI自定义导航

2010-05-11 13:16:21

Unix awk

2009-09-07 22:00:15

LINQ自定义

2011-06-15 09:24:36

Qt Widget Model

2011-08-12 18:18:03

iPhone开发UIPageContr按钮

2009-11-10 15:07:11

VB.NET窗体

2015-06-10 10:54:24

自定义路PHP

2022-05-18 07:44:13

自定义菜单前端

2023-05-18 09:25:20

background花式文字效果

2021-09-28 06:00:01

BackgroundCSS技巧
点赞
收藏

51CTO技术栈公众号