Android 自定义 ItemDecoration 分割线

01.需要实现的分割线功能

  • 需要实现的分割线功能
    • 可以设置分割线的颜色,宽度,以及到左右两边的宽度间距。

02.ItemDecoration方法说明

  • 几个重要的方法说明

    • 需要自定义类实现RecyclerView.ItemDecoration类,并选择重写合适方法。注意下面这三个方法有着强烈的因果关系!
    1
    2
    3
    4
    5
    6
    //获取当前view的位置信息,该方法主要是设置条目周边的偏移量
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
    //在item背后draw
    public void onDraw(Canvas c, RecyclerView parent, State state)
    //在item上边draw
    public void onDrawOver(Canvas c, RecyclerView parent, State state)

03.方法调用顺序

  • 注意的是三个方法的调用顺序
    • 首先调用的是getItemOffsets会被多次调用,在layoutManager每次测量可摆放的view的时候回调用一次,在当前状态下需要摆放多少个view这个方法就会回调多少次。
    • 其次会调用onDraw方法,ItemDecoration的onDraw方法是在RecyclerView的onDraw方法中调用的,注意这时候传入的canvas是RecyclerView的canvas,要时刻注意这点,它是和RecyclerView的边界是一致的。这个时候绘制的内容相当于背景,会被item覆盖。
    • 最后调用的是onDrawOver方法,ItemDecoration的onDrawOver方法是在RecyclerView的draw方法中调用的,同样传入的是RecyclerView的canvas,这时候onlayout已经调用,所以此时绘制的内容会覆盖item。

04.实现item分割线思路

  • 为每个item实现索引的思路
    • 要实现上面的可以设置分割线颜色和宽度,肯定是要绘制的,也就是需要使用到onDraw方法。那么在getItemOffsets方法中需要让view摆放位置距离bottom的距离是分割线的宽度。
    • 然后通过parent.getChildCount()方法拿到当前显示的view的数量[注意,该方法并不会获取不显示的view的数量],循环遍历后,直接用paint画笔进行绘制[注意至于分割线的颜色就是需要设置画笔的颜色]。

05.完整性代码展示

  • 代码如下所示

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
    109
    110
    111
    112
    113
    114
    115
    116
    117
    118
    119
    120
    121
    122
    123
    124
    125
    126
    127
    128
    129
    130
    131
    132
    133
    134
    135
    136
    137
    138
    139
    140
    141
    142
    143
    144
    145
    146
    147
    148
    149
    150
    151
    152
    153
    154
    155
    156
    157
    158
    159
    160
    161
    162
    163
    164
    165
    166
    167
    168
    169
    170
    171
    172
    173
    174
    175
    /**
    * list条目的分割线,可以设置线条颜色和宽度
    */
    public class RecycleViewItemLine extends RecyclerView.ItemDecoration {

    private Paint mPaint;
    private Drawable mDivider;
    /**
    * 分割线高度,默认为1px
    */
    private int mDividerHeight = 1;
    /**
    * 列表的方向:LinearLayoutManager.VERTICAL或LinearLayoutManager.HORIZONTAL
    */
    private int mOrientation;
    private static int[] ATTRS = new int[]{android.R.attr.listDivider};

    /**
    * 默认分割线:高度为2px,颜色为灰色
    * @param context 上下文
    * @param orientation 列表方向
    */
    public RecycleViewItemLine(Context context, int orientation) {
    if (orientation != LinearLayoutManager.VERTICAL &&
    orientation != LinearLayoutManager.HORIZONTAL) {
    throw new IllegalArgumentException("请输入正确的参数!");
    }
    mOrientation = orientation;
    final TypedArray a = context.obtainStyledAttributes(ATTRS);
    mDivider = a.getDrawable(0);
    a.recycle();
    }

    /**
    * 自定义分割线
    * @param context 上下文
    * @param orientation 列表方向
    * @param drawableId 分割线图片,或者shape图形
    */
    public RecycleViewItemLine(Context context, int orientation, int drawableId) {
    this(context, orientation);
    mDivider = ContextCompat.getDrawable(context, drawableId);
    if (mDivider != null) {
    mDividerHeight = mDivider.getIntrinsicHeight();
    }
    }

    /**
    * 自定义分割线
    * @param context 上下文
    * @param orientation 列表方向
    * @param dividerHeight 分割线高度
    * @param dividerColor 分割线颜色
    */
    public RecycleViewItemLine(Context context, int orientation,
    int dividerHeight, int dividerColor) {
    this(context, orientation);
    mDividerHeight = dividerHeight;
    mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
    //设置画笔paint的颜色
    mPaint.setColor(dividerColor);
    //设置样式
    mPaint.setStyle(Paint.Style.FILL);
    }


    /**
    * 调用的是getItemOffsets会被多次调用,在layoutManager每次测量可摆放的view的时候回调用一次,
    * 在当前状态下需要摆放多少个view这个方法就会回调多少次。
    * @param outRect 核心参数,这个rect相当于item摆放的时候设置的margin,
    * rect的left相当于item的marginLeft,
    * rect的right相当于item的marginRight
    * @param view 当前绘制的view,可以用来获取它在adapter中的位置
    * @param parent recyclerView
    * @param state 状态,用的很少
    */
    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view,
    @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
    super.getItemOffsets(outRect, view, parent, state);
    //给bottom留出一个高度为mDividerHeight的空白
    //这样做的目的是什么呢?就是为下面onDraw方法绘制高度为mDividerHeight的分割线做准备用的
    outRect.set(0, 0, 0, mDividerHeight);
    RefreshLogUtils.d("RecycleViewItemLine-------"+"getItemOffsets");
    }

    /**
    * 绘制分割线
    * ItemDecoration的onDraw方法是在RecyclerView的onDraw方法中调用的
    * 注意这时候传入的canvas是RecyclerView的canvas,要时刻注意这点,它是和RecyclerView的边界是一致的。
    * 这个时候绘制的内容相当于背景,会被item覆盖。
    * @param c canvas用来绘制的画板
    * @param parent recyclerView
    * @param state 状态,用的很少
    */
    @Override
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent,
    @NonNull RecyclerView.State state) {
    super.onDraw(c, parent, state);
    if (mOrientation == LinearLayoutManager.VERTICAL) {
    drawVertical(c, parent);
    } else {
    drawHorizontal(c, parent);
    }
    RefreshLogUtils.d("RecycleViewItemLine-------"+"onDraw");
    }

    /**
    * 绘制分割线
    * ItemDecoration的onDrawOver方法是在RecyclerView的draw方法中调用的
    * 同样传入的是RecyclerView的canvas,这时候onLayout已经调用,所以此时绘制的内容会覆盖item。
    * @param c canvas用来绘制的画板
    * @param parent recyclerView
    * @param state 状态,用的很少
    */
    @Override
    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent,
    @NonNull RecyclerView.State state) {
    super.onDrawOver(c, parent, state);
    RefreshLogUtils.d("RecycleViewItemLine-------"+"onDrawOver");
    }

    /**
    * 绘制横向 item 分割线
    */
    private void drawHorizontal(Canvas canvas, RecyclerView parent) {
    final int left = parent.getPaddingLeft();
    final int right = parent.getMeasuredWidth() - parent.getPaddingRight();
    RefreshLogUtils.d("小杨逗比左右的间距分别是" + left + "----"+right);
    //获取的当前显示的view的数量,并不会获取不显示的view的数量。
    //假如recyclerView里共有30条数据,而当前屏幕内显示的只有5条,这paren.getChildCount的值是5,不是30。
    final int childSize = parent.getChildCount();
    for (int i = 0; i < childSize; i++) {
    //获取索引i处的控件view
    final View child = parent.getChildAt(i);
    //拿到layoutParams属性
    RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams)
    child.getLayoutParams();
    final int top = child.getBottom() + layoutParams.bottomMargin;
    final int bottom = top + mDividerHeight;
    if (mDivider != null) {
    mDivider.setBounds(left, top, right, bottom);
    mDivider.draw(canvas);
    }
    //使用画笔paint进行绘制
    if (mPaint != null) {
    canvas.drawRect(left, top, right, bottom, mPaint);
    }
    }
    }

    /**
    * 绘制纵向 item 分割线
    */
    private void drawVertical(Canvas canvas, RecyclerView parent) {
    final int top = parent.getPaddingTop();
    final int bottom = parent.getMeasuredHeight() - parent.getPaddingBottom();
    final int childSize = parent.getChildCount();
    for (int i = 0; i < childSize; i++) {
    final View child = parent.getChildAt(i);
    RecyclerView.LayoutParams layoutParams = (RecyclerView.LayoutParams)
    child.getLayoutParams();
    final int left = child.getRight() + layoutParams.rightMargin;
    final int right = left + mDividerHeight;
    if (mDivider != null) {
    mDivider.setBounds(left, top, right, bottom);
    mDivider.draw(canvas);
    }
    if (mPaint != null) {
    canvas.drawRect(left, top, right, bottom, mPaint);
    }
    }
    }

    }