欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

【Android】RecyclerView添加分割线最易懂解析

程序员文章站 2022-06-01 18:17:27
...

【Android】RecyclerView添加分割线最易懂解析

RecyclerView可以说是开发一个Android程序必不可少的一个控件,给它的item添加分割线也是一个几乎都会有的需求。但是,相对于曾经的ListView,RecyclerView添加分割线可就麻烦多了。网上的其他教程感觉又不是很好理解。所以一直也没有好好看个知识点。

平时自己一般都是直接给item下面加一个横线。每个item都有个横线,就形成了分割线的样子。比如把item写成这样。

【Android】RecyclerView添加分割线最易懂解析

一般情况下,出来的效果也还能接受。
【Android】RecyclerView添加分割线最易懂解析

不过,这种写法虽然简单,但是体验并不好。相信大家都深有体会。比如,我想要RecyclerView上下有一些margin时。不管是滑动的边界,还是滑动的位置,给人的体验都不是很好,最后一条item底部还多出了一条没用的分割线。在item中设置margin的话,虽然可以解决,但是又需要对item的第一条和最后一天做单独处理。反而更加的麻烦。

【Android】RecyclerView添加分割线最易懂解析

这时候,我们不得不抓紧学习一下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偏移量也都设置了一点,然后运行看看有什么效果。

【Android】RecyclerView添加分割线最易懂解析

实践永远都是检验“真理”的最好方法。通过实际测试,我们知道了,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

【Android】RecyclerView添加分割线最易懂解析

我们再仔细看一下绘制背景色,也就是蓝色长方形的部分,当item画到那里的时候,与item的偏移量配合,已经有分割线的感觉了,而前景色也就是红色长方形部分,在这里我们基本是用不到的。

而通过这两点,我们可以得出结论。

通过addItemDecoration设置分割线,其实是通过item边距和背景色间接实现的。

我们直接把蓝色背景色画满屏幕。让item只偏移上下。再看一下效果。

【Android】RecyclerView添加分割线最易懂解析

到这里,最简单的一个分割线已经完成了。但是好想和在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", "--");
}

【Android】RecyclerView添加分割线最易懂解析

如果滑动是会重绘的,那分割线随着我们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的效果了。

【Android】RecyclerView添加分割线最易懂解析

如果想给某个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呗,有问题也互相交流吧!