RecyclerView ItemDecoration 完全解析

我们都知道,使用 RecyclerView 时 ,我们不能像 ListView 那样通过 setDivider() 的方式来设置分割线,好在 Android 为我们提供了定制性更强的 ItemDecoration 来为 RecyclerView 设置分割线。

什么是 ItemDecoration ?

顾名思义 ItemDecoration 就是 Item 的装饰,我们可以在 Item 的上下左右添加自定义的装饰,比如 横线,图案。同时系统已经为我们提供了一个 DividerItemDecoration, 如果这个 DividerItemDecoration 不满足我们的需求,我们就可以通过自定义 ItemDecoration 来实现了。

下面我们看下系统的 DividerItemDecoration :

引入 DividerItemDecoration(系统提供)

DividerItemDecoration 的使用非常简单,只需添加下面代码即可:

DividerItemDecoration decoration = new DividerItemDecoration(this,DividerItemDecoration.VERTICAL);
recyclerView.addItemDecoration(decoration);

效果:


DividerItemDecoration.png

如果想要修改 DividerItemDecoration 的颜色和高度,可以调用它的 setDrawable(drawable) 设置一个 Drawable 对象

// MainActivity.java
DividerItemDecoration decoration = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
Drawable dividerDrawable = getResources().getDrawable(R.drawable.drawable_divider);
decoration.setDrawable(dividerDrawable);
recyclerView.addItemDecoration(decoration);

// res/drawable/drawable_divider.xml
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
    android:shape="rectangle">
    <!-- 渐变色 -->
    <gradient
        android:angle="135"
        android:centerColor="#4CAF50"
        android:endColor="#2E7D32"
        android:startColor="#81C784"
        android:type="linear" />

    <size android:height="1dp" />
</shape>

效果:

dividerItemDecoration-drawable.png

自定义 ItemDecoration

自定义 ItemDecoration ,主要需要重写以下三个方法:

/**
 * 自定义 ItemDecoration
 */
public class LinearItemDecoration extends RecyclerView.ItemDecoration {

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
    }

    @Override
    public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDraw(c, parent, state);
    }

    @Override
    public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDrawOver(c, parent, state);
    }

1. getItemOffsets()

getItemOffsets() 主要作用是在 item 的四周留下边距,效果和 margin 类似,item 的四周留下边距后,我们就可以通过 onDraw() 在这个边距上绘制了。

public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) 

(1) 参数 Rect outRect : 表示 item 的上下左右所留下的边距。其中 outRect 的 left, top,right,bottom 即为 item 四周留下的边距的距离,默认都为 0 ;示意图如下:

(2) 参数 View view : 指当前 item 的 View 对象;

(3) 参数 RecyclerView parent : 指 RecyclerView 本身;

(4) RecyclerView.State state : 指 RecyclerView 当前的状态;

1.1 getItemOffsets() 应用例子:

既然 getItemOffsets(Rect outRect) 方法可以设置 item 四周的边距大小,那就可以设置 recyclerview 背景色和 item 四周的边距,使得 item 四周的边距透出 recyclerview 背景色来达到分割线的目的。

当然 item 的背景色需要和 recyclerview 的背景色不一致才有效果;

首先将 recyclerview 的背景色设置为 colorAccent 红色, 将 item 的背景色设置为 白色:

<!--- activity_main.xml -----> 
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorAccent" />

</android.support.constraint.ConstraintLayout>
<!--- item_recycler.xml -----> 
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="50dp"
    android:background="#fff"
    android:gravity="center_vertical"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="40dp"
        android:gravity="center_vertical" />

</LinearLayout>

然后继承 ItemDecoration 类,重写 getOffsets() 方法,将 outRect 的上边距 top 设置为 10px;

/**
 * 自定义 ItemDecoration
 */
public class LinearItemDecoration extends RecyclerView.ItemDecoration {

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.top = 10;   // 10px
    }
}

最后将这个 LinearItemDecoration 添加到 RecyclerView 中:

LinearItemDecoration decoration = new LinearItemDecoration();
recyclerView.addItemDecoration(decoration);

效果如下(下方的红色是 因为RecyclerView 高度为 match_parent ,但 item 数据只有 5 条):

linearItemDecoration.png

从效果图可以看出:每一个 item 的 顶部都有一个红色的背景线,包括第一个 item 顶部也有(怎么解决呢?见 2.1 节详解)

同理,我们可以设置为 底部 10px,左侧 20px ,右侧 40px;

public class LinearItemDecoration extends RecyclerView.ItemDecoration {

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.bottom = 10;
        outRect.left = 20;
        outRect.right = 40;
    }
}

效果:


linearItemDecoration-bottom-left-right.png

可以看到:每个 item 的左测,底部,右侧都有了间距,露出了 RecyclerView 的背景色了。

2. onDraw()

@Override
public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
   super.onDraw(canvas, parent, state);
}

onDraw() 函数中的 parent , state 参数和 getItemOffsets() 方法中的参数含义是一样的,canvas 参数是 getItemOffsets() 函数所留下的左右上下的空白区域对应的 Canvas 画布对象。我们可以在这个区域中利用 Paint 画笔绘制任何图形。

比如在 item 左侧绘制一个 空心圆。

/**
 * 自定义 ItemDecoration
 */
public class LinearItemDecoration extends RecyclerView.ItemDecoration {

    private static final String TAG = "LinearItemDecoration";
    private Paint paint;

    public LinearItemDecoration() {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(5);
    }

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        Log.e(TAG, "getItemOffsets: " );
        outRect.bottom = 10;
        outRect.left = 100;
        outRect.right = 40;
    }

    @Override
    public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDraw(canvas, parent, state);
        Log.e(TAG, "onDraw: ");
        for (int i = 0; i < parent.getChildCount(); i++) {
            View childView = parent.getChildAt(i);
            canvas.drawCircle(50, childView.getTop() + childView.getHeight() / 2, 20, paint);
        }

    }
}

效果:


onDraw-circle.png

我们在 getItemOffsets() 和 onDraw() 方法中都添加了日志,日志打印如下:

log.png

从日志中可以看出: getItemOffsets() 方法执行了 5 遍,和 数据源个数是一样的,但 onDraw() 方法只执行了一遍,由此我们知道, getItemOffsets() 是针对每个 item 都会执行一次,也就是说 每个 item 的 outRect 可以设置为不同值,但是 onDraw(),onDrawOver() 是针对 ItemDecoration 的,不是针对 item 的,只执行一次。所以我们在 onDraw() ,onDrawOver() 中绘制的时候,需要遍历每个 item 进行绘制。

优雅获取 outRect 中的值

在上面例子中,我们在 onDraw 中获取 outRect 中的值都是写成计算好的固定值,显然这种硬编码的方式不利于扩展,其实,我们可以通过 LayoutManager 来获取 getItemOffsets() 中设置的 outRect 的值。

@Override
public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
    super.onDraw(canvas, parent, state);
    Log.e(TAG, "onDraw: ");
    RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
    for (int i = 0; i < parent.getChildCount(); i++) {
        View childView = parent.getChildAt(i);
         int leftDecorationWidth = layoutManager.getLeftDecorationWidth(childView);
         int topDecorationHeight = layoutManager.getTopDecorationHeight(childView);
         int rightDecorationWidth = layoutManager.getRightDecorationWidth(childView);
         int bottomDecorationHeight = layoutManager.getBottomDecorationHeight(childView);
    }
}

上面硬编码可以改成:

@Override
public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
    super.onDraw(canvas, parent, state);
    Log.e(TAG, "onDraw: ");
    RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
    for (int i = 0; i < parent.getChildCount(); i++) {
        View childView = parent.getChildAt(i);
        int leftDecorationWidth = layoutManager.getLeftDecorationWidth(childView);
        int left = leftDecorationWidth / 2;
        canvas.drawCircle(left, childView.getTop() + childView.getHeight() / 2, 20, paint);
    }
}
2.1 扩展1 -- 减少背景设置,避免过度绘制

为了减少过度绘制,我们将 activity_main 中 RecyclerView , item_recycler 中的背景全部去掉,不设置任何背景,然后在 LinearItemDecoration 中进行纯绘制分割线,代码如下:

public class LinearItemDecoration extends RecyclerView.ItemDecoration {

    private static final String TAG = "LinearItemDecoration";
    private Paint paint;
    private Paint dividerPaint;

    public LinearItemDecoration() {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(5);

        dividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        dividerPaint.setColor(Color.parseColor("#e6e6e6"));
        dividerPaint.setStyle(Paint.Style.FILL);
    }

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        Log.e("ItemOffsets", "getItemOffsets: ");
        outRect.bottom = 5;
        outRect.left = 100;
    }

    @Override
    public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDraw(canvas, parent, state);
        Log.e(TAG, "onDraw: ");
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        for (int i = 0; i < parent.getChildCount(); i++) {
            View childView = parent.getChildAt(i);
            int leftDecorationWidth = layoutManager.getLeftDecorationWidth(childView);
            int topDecorationHeight = layoutManager.getTopDecorationHeight(childView);
            int rightDecorationWidth = layoutManager.getRightDecorationWidth(childView);
            int bottomDecorationHeight = layoutManager.getBottomDecorationHeight(childView);
            int left = leftDecorationWidth / 2;
            canvas.drawCircle(left, childView.getTop() + childView.getHeight() / 2, 20, paint);
            // getItemOffsets()中的设置的是 bottom = 5px;所以在 drawRect 时,top 为 childView.getBottom,bottom为top+bottomDecorationHeight
            canvas.drawRect(new Rect(
                    leftDecorationWidth,
                    childView.getBottom(),
                    childView.getWidth() + leftDecorationWidth,
                    childView.getBottom() + bottomDecorationHeight
            ), dividerPaint);
        }
    }

    @Override
    public void onDrawOver(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDrawOver(canvas, parent, state);
    }
}

效果:

divider-draw.png

注意: 上面 getItemOffsets() 中设置的是 bottom = 5px; 所以在 onDraw() 方法的 drawRect 时,top值为 childView.getBottom, bottom值为 top+bottomDecorationHeight。

divider-top-bottom.png

同理:getItemOffsets() 中设置是 top = 5px, 那么在 onDraw() 方法 drawRect 时,bottom 值为 childView.getTop() , top 值为 bottom - topDecorationHeight

2.2 扩展2 -- 实现竖直进度分割线

在 getItemOffsets() 方法中左侧留下空白区域,然后在 onDraw() 方法中绘制 圆和 竖线。代码如下:

<!--- item_recycler.xml ---->
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@android:color/white"
    android:gravity="center_vertical"
    android:orientation="vertical"
    android:padding="10dp">

    <TextView
        android:id="@+id/tv_title"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textColor="#332"
        android:textSize="16sp"
        tools:text="我是 title" />

    <TextView
        android:id="@+id/tv_time"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="5dp"
        android:gravity="center_vertical"
        android:textColor="#666"
        android:textSize="14sp"
        tools:text="woshi" />

</LinearLayout>

<!--- activity_main.xml --->
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rv_recycler"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#dfdfdf" />

</android.support.constraint.ConstraintLayout>
// ProgressItemDecoration.java
public class ProgressItemDecoration extends RecyclerView.ItemDecoration {

    private Context context;
    private Paint circlePaint;
    private Paint linePaint;
    private int radius;
    private int curPosition = 0;   // 当前进行中的位置

    public ProgressItemDecoration(Context context) {
        this.context = context;
        circlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        circlePaint.setColor(context.getResources().getColor(R.color.colorPrimary));
        circlePaint.setStyle(Paint.Style.FILL);
        radius = dp2Px(8);
        circlePaint.setStrokeWidth(dp2Px(2));


        linePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        linePaint.setColor(context.getResources().getColor(R.color.colorPrimary));
        linePaint.setStrokeWidth(dp2Px(2));
    }

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.top = dp2Px(20);
        outRect.left = dp2Px(50);
        outRect.right = dp2Px(20);

    }

    @Override
    public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDraw(canvas, parent, state);
        int childCount = parent.getChildCount();
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        for (int i = 0; i < childCount; i++) {
            View childView = parent.getChildAt(i);
            int leftDecorationWidth = layoutManager.getLeftDecorationWidth(childView);
            int topDecorationHeight = layoutManager.getTopDecorationHeight(childView);
            // 获取当前 item 是 recyclerview 的第几个 childview
            int childLayoutPosition = parent.getChildLayoutPosition(childView);
            float startX = leftDecorationWidth / 2;
            float stopX = startX;
            // 圆顶部部分竖线,起点 Y
            float topStartY = childView.getTop() - topDecorationHeight;
            // 圆顶部部分竖线,终点 Y
            float topStopY = childView.getTop() + childView.getHeight() / 2 - radius;

            // 圆底部部分竖线,起点 Y
            float bottomStartY = childView.getTop() + childView.getHeight() / 2 + radius;
            // 圆底部部分竖线,终点 Y
            float bottomStopY = childView.getBottom();

            // 位置超过 curPosition 时,竖线颜色设置为浅色
            if (childLayoutPosition > curPosition) {
                linePaint.setColor(context.getResources().getColor(R.color.colorPrimaryTint));
                circlePaint.setColor(context.getResources().getColor(R.color.colorPrimaryTint));
                circlePaint.setStyle(Paint.Style.STROKE);
            } else {
                linePaint.setColor(context.getResources().getColor(R.color.colorPrimary));
                circlePaint.setColor(context.getResources().getColor(R.color.colorPrimary));
                circlePaint.setStyle(Paint.Style.FILL);
            }

            // 绘制圆
            if (childLayoutPosition == curPosition) {
                circlePaint.setStyle(Paint.Style.STROKE);
                canvas.drawCircle(leftDecorationWidth / 2, childView.getTop() + childView.getHeight() / 2, dp2Px(2), circlePaint);
            }
            canvas.drawCircle(leftDecorationWidth / 2, childView.getTop() + childView.getHeight() / 2, radius, circlePaint);

            // 绘制竖线 , 第 0 位置上只需绘制 下半部分
            if (childLayoutPosition == 0) {
                // 当前 item position = curPosition 时,绘制下半部分竖线时,颜色设置为浅色
                if (childLayoutPosition == curPosition) {
                    linePaint.setColor(context.getResources().getColor(R.color.colorPrimaryTint));
                }
                canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint);
                // 最后位置上,只需绘制上半部分
            } else if (childLayoutPosition == parent.getAdapter().getItemCount() - 1) {
                canvas.drawLine(startX, topStartY, startX, topStopY, linePaint);
            } else {
                // 都要绘制
                canvas.drawLine(startX, topStartY, startX, topStopY, linePaint);
                // 当前 item position = curPosition 时,绘制下半部分竖线时,颜色设置为浅色
                if (childLayoutPosition == curPosition) {
                    linePaint.setColor(context.getResources().getColor(R.color.colorPrimaryTint));
                }
                canvas.drawLine(startX, bottomStartY, startX, bottomStopY, linePaint);
            }
        }

    }

    /**
     * 设置进行中的位置
     *
     * @param recyclerView
     * @param position
     */
    public void setDoingPosition(RecyclerView recyclerView, int position) {
        if (recyclerView == null) {
            throw new IllegalArgumentException("RecyclerView can't be null");
        }
        if (recyclerView.getAdapter() == null) {
            throw new IllegalArgumentException("RecyclerView Adapter can't be null");
        }
        if (position < 0) {
            throw new IllegalArgumentException("position can't be less than 0");
        }
        recyclerView.getLayoutManager().getItemCount();
        if (position > recyclerView.getAdapter().getItemCount() - 1) {
            throw new IllegalArgumentException("position can't be greater than item count");
        }
        this.curPosition = position;
    }

    private int dp2Px(int value) {
        return (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, value, context.getResources().getDisplayMetrics());
    }
}

// MainActivity.java
public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        RecyclerView recyclerView = findViewById(R.id.rv_recycler);
        recyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            String data = "test data  - " + i;
            if (i % 2 == 0) {
                data = data + "\n" + "这是测试数据 " + i;
            }
            list.add(data);
        }
        BaseRecyclerAdapter adapter = new BaseRecyclerAdapter<String>(list, R.layout.item_recycler) {

            @Override
            protected void bind(BaseRecyclerAdapter<String> adapter, BaseViewHolder holder, String data, int position) {
                holder.setText(R.id.tv_title, data);
                holder.setText(R.id.tv_time, new Date().toString());
            }
        };
        recyclerView.setAdapter(adapter);
        ProgressItemDecoration decoration = new ProgressItemDecoration(this);
        decoration.setDoingPosition(recyclerView, 0);
        recyclerView.addItemDecoration(decoration);
    }
}

效果:

vertical-progress.gif

3. onDrawOver()

当我们将上面例子中绘制左侧的空心圆的位置改为: 圆心 x 坐标为 leftDecorationWidth,同时将 item 背景色设置为 白色:

 @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        Log.e("ItemOffsets", "getItemOffsets: ");
        outRect.bottom = 5;
        outRect.left = 100;
    }

    @Override
    public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDraw(canvas, parent, state);
        Log.e(TAG, "onDraw: ");
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        for (int i = 0; i < parent.getChildCount(); i++) {
            View childView = parent.getChildAt(i);
            int leftDecorationWidth = layoutManager.getLeftDecorationWidth(childView);
            int bottomDecorationHeight = layoutManager.getBottomDecorationHeight(childView);
            int left = leftDecorationWidth / 2;
//            canvas.drawCircle(left, childView.getTop() + childView.getHeight() / 2, 20, paint);
            canvas.drawCircle(leftDecorationWidth, childView.getTop() + childView.getHeight() / 2, 20, paint);
            // getItemOffsets()中的设置的是 bottom = 5px;所以在 drawRect 时,top 为 childView.getBottom,bottom为top+bottomDecorationHeight
            canvas.drawRect(new Rect(
                    leftDecorationWidth,
                    childView.getBottom(),
                    childView.getWidth() + leftDecorationWidth,
                    childView.getBottom() + bottomDecorationHeight
            ), dividerPaint);
        }
    }

效果:

draw-leftDecoration-width.png

我们发现:绘制的空心圆被 item 遮挡了右边部分,变为不可见了,这是因为在这个绘制的流程中,先调用 ItemDecoration 的 onDraw() 方法,然后再调用 item 的 onDraw() 方法,最后再调用 ItemDecoration 的 onDrawOver() 方法。

因此,当我们想要在 item 的绘制显示一些内容时,将绘制的逻辑写在 onDrawOver() 方法即可。

下面我们实现在 item 与 左侧 decoration 交汇处绘制一个 apple icon ,并将 item 中的内容文本设置为居中显示,代码如下:

public class LinearItemDecoration extends RecyclerView.ItemDecoration {

    private static final String TAG = "LinearItemDecoration";
    private Paint paint;
    private Paint dividerPaint;
    private Bitmap iconBitmap;

    public LinearItemDecoration(Context context) {
        paint = new Paint(Paint.ANTI_ALIAS_FLAG);
        paint.setColor(Color.BLUE);
        paint.setStyle(Paint.Style.STROKE);
        paint.setStrokeWidth(5);

        dividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        dividerPaint.setColor(Color.parseColor("#e6e6e6"));
        dividerPaint.setStyle(Paint.Style.FILL);

        iconBitmap = BitmapFactory.decodeResource(context.getResources(), R.drawable.ios);
    }

    @Override
    public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        Log.e("ItemOffsets", "getItemOffsets: ");
        outRect.bottom = 5;
        outRect.left = 100;
    }

    @Override
    public void onDraw(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDraw(canvas, parent, state);
        Log.e(TAG, "onDraw: ");
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        for (int i = 0; i < parent.getChildCount(); i++) {
            View childView = parent.getChildAt(i);
            int leftDecorationWidth = layoutManager.getLeftDecorationWidth(childView);
            int bottomDecorationHeight = layoutManager.getBottomDecorationHeight(childView);
            int left = leftDecorationWidth / 2;
//            canvas.drawCircle(left, childView.getTop() + childView.getHeight() / 2, 20, paint);
//            canvas.drawCircle(leftDecorationWidth, childView.getTop() + childView.getHeight() / 2, 20, paint);

            // getItemOffsets()中的设置的是 bottom = 5px;所以在 drawRect 时,top 为 childView.getBottom,bottom为top+bottomDecorationHeight
            canvas.drawRect(new Rect(
                    leftDecorationWidth,
                    childView.getBottom(),
                    childView.getWidth() + leftDecorationWidth,
                    childView.getBottom() + bottomDecorationHeight
            ), dividerPaint);
        }
    }

    @Override
    public void onDrawOver(@NonNull Canvas canvas, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
        super.onDrawOver(canvas, parent, state);
        RecyclerView.LayoutManager layoutManager = parent.getLayoutManager();
        for (int i = 0; i < parent.getChildCount(); i++) {
            View childView = parent.getChildAt(i);
            int leftDecorationWidth = layoutManager.getLeftDecorationWidth(childView);
            canvas.drawBitmap(iconBitmap, leftDecorationWidth - iconBitmap.getWidth() / 2,
                    childView.getTop() + childView.getHeight() / 2 - iconBitmap.getHeight() / 2, paint);
        }
    }
}

效果:

drawover-ios.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,980评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,178评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,868评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,498评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,492评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,521评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,910评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,569评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,793评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,559评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,639评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,342评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,931评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,904评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,144评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,833评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,350评论 2 342

推荐阅读更多精彩内容