前言
RecyclerView.ItemDecoration是用于实现RecyclerView的Item间距,当然除了实现间距更酷炫的是它可以实现一些在间距上绘制各种分割线。绘制分割线也还是一般操作,深度了解后你甚至可以实现各种时间轴,item分组标题等等功能。因为提供了onDraw方法与Canvas,所以在绘制上自由度极大,可以让你实现各种天马行空的效果。
主要的三个重写方法
ItemDecoration只有3个重要的重写方法:
- getItemOffsets 用于实现item的上下左右的间距大小
- onDraw 在这个方法里绘制的文字、颜色、图形都会比item更低一层,这些绘制效果如果与item重叠,就会被item遮盖
- onDrawOver 在这个方法绘制的文字、颜色、图形都会比item更高一层,这些绘制效果始终在最上层,不会被遮盖。
我们逐一了解这些方法如何使用。
getItemOffsets
首先先要写一个RecyclerView列表的Demo来进行演示。这里实现了一个item的背景为蓝色的LinearLayoutManager的列表,如下图:
给列表的item增加上边距
代码如下:
public class MainActivity extends AppCompatActivity { private RecyclerView mRecyclerView; private RecyclerViewAdapter mRecyclerViewAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRecyclerView = findViewById(R.id.recyclerview); mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); mRecyclerViewAdapter = new RecyclerViewAdapter(); mRecyclerView.setAdapter(mRecyclerViewAdapter); addData(); /* 将itemDecoration添加到RecyclerView。 请注意这里是add的,在底层源码里面可以看到ItemDecoration是可以被添加多个的.这里是一个RecyclerView持有ItemDecoration集合。 能添加当然就可以移除,所以对应移除的方法 mRecyclerView.removeItemDecoration();//根据目标移除 mRecyclerView.removeItemDecorationAt();//根据索引index移除 */ mRecyclerView.addItemDecoration(getItemDecoration()); } private RecyclerView.ItemDecoration getItemDecoration() { return new RecyclerView.ItemDecoration() { @Override public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { outRect.top = 20;//这里增加了20的上边距 } }; } private void addData() { List<String> list = new ArrayList<>(); list.add("猎户座"); list.add("织女座"); list.add("天马座"); list.add("天秤座"); list.add("剑鱼座"); list.add("飞马座"); list.add("三角座"); list.add("天琴座"); list.add("蛇夫座"); mRecyclerViewAdapter.refreshData(list); } }
请注意!在首次触发的getItemOffsets返回的View都是没有经过measure测量的,所以这里的View没有尺寸值。但是滚动后重新触发的getItemOffsets返回的View就有了尺寸值。但是这里返回的RecyclerView是已经measure测量过了。
效果图:
举一反三,我们可以给左边添加边距
代码:
private RecyclerView.ItemDecoration getItemDecoration() { return new RecyclerView.ItemDecoration() { @Override public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { outRect.top = 20; outRect.left = 100; } }; }
效果图:
给指定位置的Item设置边距
有时候,我们的边距需求并不是全部item都是要求有的,比如我们需求“第一个item有 50 的上边距与最后一个item要求有 50 的下边距”。我们可以根据getItemOffsets 方法提供的 RecyclerView, RecyclerView.State 这两个值来确定需要实现边距的指定item。
代码:
private RecyclerView.ItemDecoration getItemDecoration() { return new RecyclerView.ItemDecoration() { @Override public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { if (parent.getChildAdapterPosition(view) == 0){ //给第一位的item设置50上边距 outRect.top = 50; return; } if (parent.getChildAdapterPosition(view) == state.getItemCount() -1){ //给最后一位的item设置50下边距 outRect.bottom = 50;
return; } } }; }
效果图:
onDraw
在重写实现getItemOffsets方法给item增加边距后,我们可以在onDraw方法实现一些文字,图标等等效果。另外Draw其实是自定义View的知识,如果你还没了解过Android 的Draw是如何实现的,你应该先去了解自定义View。
给空白边距里绘制文字
代码:
private RecyclerView.ItemDecoration getItemDecoration() { return new RecyclerView.ItemDecoration() { private Paint paint = new Paint(); @Override public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { outRect.top = 20; } @Override public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { int count = parent.getChildCount(); //获得当前RecyclerView数量 paint.setColor(Color.RED); //设置画笔为红色 paint.setTextSize(20); //设置文字大小 for (int i = 0; i < count; i++) { //遍历全部item View View view = parent.getChildAt(i); int top = view.getTop(); //获得这个item View的top位置 int bottom = view.getBottom(); int left = view.getLeft(); int right = view.getRight(); c.drawText("第" + i, left, top, paint); } } }; }
这里有些人会有一些误区,认为这里返回的canvas是某一个item下的canvas。(我之前有这样的理解)实际上这里返回的canvas是整个RecyclerView的canvas,如果你把坐标值固定死,也是在RecyclerView里面某个位置绘制这个文字或者图像。所以,这里需要你自己获取全部Item的坐标值,用获取到的Item坐标值来绘制你需要位置上的内容。
效果图:
给空白边距里绘制分割线
这里提醒,除了绘制shape分割线,其实还可以使用xml矢量图来绘制图标。
先实现一个shape分割线:
shape_gray_line.xml
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="line"> <stroke android:width="5dp" android:color="@color/yellow_color"/> </shape>
代码:
private Drawable mDividingLineDrawable; private RecyclerView.ItemDecoration getItemDecoration() { mDividingLineDrawable = getDrawable(R.drawable.shape_gray_line); return new RecyclerView.ItemDecoration() { @Override public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { outRect.top = 20; } @Override public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { int count = parent.getChildCount(); parent.getPaddingLeft(); for (int i = 0; i < count; i++) { View view = parent.getChildAt(i); int top = view.getTop(); int bottom = view.getBottom(); int left = view.getLeft(); int right = view.getRight(); mDividingLineDrawable.setBounds(left , top - 20, right, top); mDividingLineDrawable.draw(c); } } }; }
效果图:
解释onDraw绘制在Item下层是什么效果
开头我们说过 “ 在这个方法里绘制的文字、颜色、图形都会比item更低一层,这些绘制效果如果与item重叠,就会被item遮盖 ” ,要证明这个效果很简单,我们只需要将绘制内容位置与item重叠一下就能明白效果了。
代码:
@Override public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { int count = parent.getChildCount(); paint.setColor(Color.RED); paint.setTextSize(20); for (int i = 0; i < count; i++) { View view = parent.getChildAt(i); int top = view.getTop(); int bottom = view.getBottom(); int left = view.getLeft(); int right = view.getRight(); c.drawText("第" + i, left, top + 10, paint);//这里top 增加10 让绘制文字与item重叠 } }
效果图:
可以从这个效果图看到,文字被item覆盖了。
onDrawOver
onDrawOver在使用上与onDraw上是一致的,说这里不在重复说明怎么绘制内容。 这里只解释下onDrawOver的特性与onDraw对比理解。
解释onDrawOver绘制在Item上层是什么效果
这里很简单只要在上面onDraw绘制文字的代码了复制一下到onDrawOver里实现就可以了
代码:
@Override public void onDrawOver(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { int count = parent.getChildCount(); paint.setColor(Color.RED); paint.setTextSize(20); for (int i = 0; i < count; i++) { View view = parent.getChildAt(i); int top = view.getTop(); int bottom = view.getBottom(); int left = view.getLeft(); int right = view.getRight(); c.drawText("第" + i, left, top + 10, paint);//这里top 增加10 让绘制文字与item重叠 } }
效果图:
可以从这个效果图看到,文字在最上层。
End