【Android】RecyclerView添加分割线最易懂解析
【Android】RecyclerView添加分割线最易懂解析
RecyclerView可以说是开发一个Android程序必不可少的一个控件,给它的item添加分割线也是一个几乎都会有的需求。但是,相对于曾经的ListView,RecyclerView添加分割线可就麻烦多了。网上的其他教程感觉又不是很好理解。所以一直也没有好好看个知识点。
平时自己一般都是直接给item下面加一个横线。每个item都有个横线,就形成了分割线的样子。比如把item写成这样。
一般情况下,出来的效果也还能接受。
不过,这种写法虽然简单,但是体验并不好。相信大家都深有体会。比如,我想要RecyclerView上下有一些margin时。不管是滑动的边界,还是滑动的位置,给人的体验都不是很好,最后一条item底部还多出了一条没用的分割线。在item中设置margin的话,虽然可以解决,但是又需要对item的第一条和最后一天做单独处理。反而更加的麻烦。
这时候,我们不得不抓紧学习一下RecyclerView这个简单但是常用的方法。也就是addItemDecoration这个方法。
addItemDecoration有两个构造函数,两个参数的构造函数多了一个index,看名字大概知道是只给某个item添加分割线。我们先不管它,先写出来一个再说。
先调用RecyclerView的addItemDecoration方法,然后填写参数,把方法全重写一下试试看。简单看下文档结合其他查到的教程,我们知道,RecyclerView的分割线是通过canvas和设置item偏移画出来的。所以我们通过画一些不同的线。测试一下这些重写的方法有什么作用。
recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
Paint paint = new Paint();
paint.setColor(Color.BLUE);
c.drawRect(0, 0, 3000, 100, paint);
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
Paint paint = new Paint();
paint.setColor(Color.RED);
c.drawRect(0, 100, 3000, 200, paint);
}
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.set(10, 10, 10, 10);
}
});
根据方法的名字,我们知道onDraw是画分割线的,getItemOffsets是设置item偏移的,我们把每个重写的方法都画了一个比较明显的线,item偏移量也都设置了一点,然后运行看看有什么效果。
实践永远都是检验“真理”的最好方法。通过实际测试,我们知道了,onDraw是绘制背景色的方法,onDrawOver是绘制前景色的方法。getItemOffsets是设置每个item偏移量的方法。其中getItemOffsets中outRect方形的大小,决定item与group的边距和item之间的边距。
我们再把outRect设置不同的左上右下值。看看每个值具体改变的位置。
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
outRect.set(50, 20, 100, 30);
}
运行后,我们可以知道。getItemOffsets中的参数Rect的left、top、right、bottom的值即为每个item距离左上右下的距离。
虽然不严谨,但是可以简单的理解为:Rect.set中的四个参数为item的 marginLeft , marginTop , marginRight , marginBottom
我们再仔细看一下绘制背景色,也就是蓝色长方形的部分,当item画到那里的时候,与item的偏移量配合,已经有分割线的感觉了,而前景色也就是红色长方形部分,在这里我们基本是用不到的。
而通过这两点,我们可以得出结论。
通过addItemDecoration设置分割线,其实是通过item边距和背景色间接实现的。
我们直接把蓝色背景色画满屏幕。让item只偏移上下。再看一下效果。
到这里,最简单的一个分割线已经完成了。但是好想和在item中设置并没有什么区别。
先别急,虽然看起来没区别,但是这里判断第一条item和最后一条item可简单多了。
我们可以让每个item分割线一样。
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int childAdapterPosition = parent.getChildAdapterPosition(view);
if (childAdapterPosition == 0) {
outRect.set(0, 20, 0, 20);
} else {
outRect.set(0, 0, 0, 20);
}
}
也可以让第一个item和最后一个item距离大一些
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
super.getItemOffsets(outRect, view, parent, state);
int childAdapterPosition = parent.getChildAdapterPosition(view);
if (childAdapterPosition == 0) {
outRect.set(0, 40, 0, 10);
} else if (childAdapterPosition == parent.getAdapter().getItemCount() - 1) {
outRect.set(0, 10, 0, 40);
} else {
outRect.set(0, 10, 0, 10);
}
}
总之,咱们可以根据需要,随意定制同一个RecyclerView中不同item分割线的宽度。
接下来就是不同item分割线的颜色了,我们可以把最顶部item的marginTop看成是第一个item上面有一个和背景颜色一样的分割线。这么理解的话,其实就是不同item分割线颜色的问题了。
更改分割线颜色还是稍微复杂一些的。同样颜色分割线,我们只需要绘制一个背景,然后设置item偏移就行了。因为线的颜色是一样的,我们不需要考虑item移动时线的位置也需要变换的问题。而颜色不一样时,线的位置是需要不停变换的。
我在onDraw方法中随便打印了一点东西,发现onDraw方法是会在item滑动时不停重绘的。毕竟这个方法中有一个RecyclerView.State参数,一直重绘也就不难猜到了。
@Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDraw(c, parent, state);
Log.i("Log", "--");
}
如果滑动是会重绘的,那分割线随着我们item位置的改变而改变就不难实现了。只需要根据item的getTop等方法即可计算出分割线的宽度和位置。
我们先实现一个RecyclerView第一和最后一个item距离顶部和底部有一定margin的布局。
onDraw中是没有view这个参数的,我们需要通过得到每个item的item的下标。也就是设置item偏移量时,判断是第几个item的参数。
for (int i = 0; i < parent.getAdapter().getItemCount(); i++) {
View view = parent.getChildAt(i);
int childAdapterPosition = parent.getChildAdapterPosition(view);
}
因为draw方法是可以覆盖的,所以我们可以先画一个大的背景色,作为平常item的分割线。
paint.setColor(Color.BLUE);
c.drawRect(0, 0, windowsWidth, parent.getHeight(), paint);
然后,判断item下标,如果是第一个,也就是下标为“0”,我们就在这个item的上面绘制一个和RecyclerView的Parent布局背景色一样的分割线,宽度为指定margin大小(在上一步的getItemOffsets方法中设置)。最后一个item同理。
int itemCount = parent.getAdapter().getItemCount();
for (int i = 0; i < itemCount; i++) {
View view = parent.getChildAt(i);
int childAdapterPosition = parent.getChildAdapterPosition(view);
if (childAdapterPosition == 0) {
paint.setColor(0xFFCCCCCC);
c.drawRect(0, 0, windowsWidth, view.getTop(), paint);
}else if (childAdapterPosition == itemCount - 1) {
paint.setColor(0xFFCCCCCC);
c.drawRect(0, view.getTop(), windowsWidth, parent.getHeight(), paint);
}
}
这样,我们的item就有marginTop和marginBottom的效果了。
如果想给某个item设置颜色,只需要用相同的办法,配合getItemOffsets方法中的偏移量就可以了。
else if (childAdapterPosition == 5) {
paint.setColor(Color.RED);
c.drawRect(0, view.getTop() - offset, windowsWidth, view.getBottom(), paint);
}
注意(自己的一点小观点):
这里我看好多博客是把每个item的分割线都在for循环中绘制一遍。这里我认为还是先绘制大背景,然后再根据个别item的不同颜色需要,来绘制比较好。(除非你每个item的分割线颜色都不一样。)放在for循环中给每个item绘制,虽然会减少过度绘制,但是会调用成倍的drawRect方法,对性能反而损耗更多。
绘制分割线需要注意的地方还很多,比如LinearLayoutManager设置reverseLayout为true时,布局方式为GridLayoutManager时。大家知道实现方式后,这些也都不算什么了,道理都是一样的。
不知道大家理解RecyclerView绘制分割线的原理了没有(虽然一直翻源码,但是也可以叫原理吧 (>﹏<) )接下来别再item中设置分割线了,写一个自己的RecyclerView分割线吧!
通用RecyclerView分割线
我这里写了一个通用的分割线,支持linearlayout,gridlayout两种布局方式,改变各种参数基本都有照顾到。大家可以试试。老规矩,看到的点个star呗,有问题也互相交流吧!
上一篇: Android自定义控件实现雷达效果
下一篇: Opencv笔记(十三)——图像的梯度