天天看点

Android RecyclerView ItemDecoration 分割线

文章目录

    • 如何设置分割线
    • DividerItemDecoration使用
    • 自定义 ItemDecoration getItemOffsets
    • 自定义 ItemDecoration onDraw onDrawOver
    • 实战1:使用 onDrawOver() 方法在 item 的上画一个半透明的蒙版
    • 实战2:自定义分割线颜色
    • 实战3:自定义分割线左侧偏移量

RecycleView

没有像

ListView

一样可以直接在 xml 中或者通过 setDivider()方法设置分割线的方法。它是通过

RecycleView

addItemDecoration(ItemDecoration decor)

方法来设置的。很显然,我们需要传入一个 ItemDecoration 对象,这个对象是一个抽象类,官方已经提供了一种常用分割线类:

DividerItemDecoration

。来看一下用法:

recyclerView.addItemDecoration(itemDecoration)
           
public void addItemDecoration(@NonNull ItemDecoration decor) {
        addItemDecoration(decor, -1);
    }
           

ItemDecoration 是一个抽象类

public abstract static class ItemDecoration {
      
        public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent, @NonNull State state) {
            onDraw(c, parent);
        }

        @Deprecated
        public void onDraw(@NonNull Canvas c, @NonNull RecyclerView parent) {
        }

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

        @Deprecated
        public void getItemOffsets(@NonNull Rect outRect, int itemPosition,
                @NonNull RecyclerView parent) {
            outRect.set(0, 0, 0, 0);
        }

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

从源码的注释文档可以看出:

  • ItemDecoration 可以为 item 添加绘图,还可以设置偏移量
  • ItemDecoration 可以用于实现 item 之间的分隔线、高亮显示、可视分组等功能
  • ItemDecoration 中的 onDraw() 方法先于 item 绘制,onDrawOver(Canvas, RecyclerView, RecyclerView.State方法执行顺序在 item 的绘制之后

DividerItemDecoration 是 Google 官方实现的分割线实现类,DividerItemDecoration 继承 ItemDecoration

public class DividerItemDecoration extends RecyclerView.ItemDecoration {
   ...
}
           

recyclerView如何使用

recyclerView.adapter = UserAdapter(getData())
recyclerView.layoutManager = LinearLayoutManager(this)
var dividerItemDecoration = DividerItemDecoration(this,DividerItemDecoration.VERTICAL)
//设置一个分割线
recyclerView.addItemDecoration(dividerItemDecoration)
           

代码运行起来,来看看效果:

Android RecyclerView ItemDecoration 分割线

recyclerView 已经显示了一条细细的分割线,美观度还算可以。

通过源码打断点调试,默认的分割线高度是 3 pix .

Android RecyclerView ItemDecoration 分割线

如果我们要改变分割线的颜色,该怎么做呢?

答案其实还挺简单,

dividerItemDecoration

需要设置一个

drawable

, 这个

drawable

就是 分割线。

drawable

文件夹里面新建

list_item_bg

文件

<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
    android:shape="rectangle">

    <!-- 自定义分割线高度 -->

    <size android:height="5dp" />

    <!-- 自定义分割线圆角 -->
    <corners android:radius="1dp" />

    <!-- 自定义分割线渐变 -->
    <gradient
        android:angle="0"
        android:endColor="#FFDC9E "
        android:startColor="#D9983B"
        android:type="linear" />
</shape>

           

修复后的代码如下:

recyclerView.adapter = UserAdapter(getData())
recyclerView.layoutManager = LinearLayoutManager(this)
var dividerItemDecoration = DividerItemDecoration(this,DividerItemDecoration.VERTICAL)
//设置一个分割线背景
dividerItemDecoration.setDrawable(resources.getDrawable(R.drawable.list_item_bg))
//添加一个分割线
recyclerView.addItemDecoration(dividerItemDecoration)
           

我们把代码运行起来,看看效果:

Android RecyclerView ItemDecoration 分割线

自定义的分割线美观度还可以,但是我们发现一个问题,recyclerView 的最后一行底部也有一个分割线,这就不太美观了,效果如下:

Android RecyclerView ItemDecoration 分割线

问题又来了,怎么去掉最后一行的分割线?答案是做不到,系统提供的 DividerItemDecoration 类没有相关api。只能我们自定义 ItemDecoration 。

第一步,重写

getItemOffsets

方法,如下

package com.cootek.recyclerviewdemo;

import android.graphics.Rect;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

/**
 * @author yanjun.zhao
 * @time 2020/12/21 2:57 PM
 * @desc
 */
class MyListItemDecoration 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);
    }
}

           
  • getItemOffsets

    可以通过

    outRect.set(l,t,r,b)

    设置指定

    itemview

    的偏移量。

接下来,我们实验一下,把每一个 item 底部都留 10 px 的位置。

/**
 * @author yanjun.zhao
 * @time 2020/12/21 2:57 PM
 * @desc
 */
class MyListItemDecoration 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 ;  //底部偏移10px
    }
}

           

使用如下:

//添加自定义 ItemDecoration
var dividerItemDecoration = MyListItemDecoration()
recyclerView.addItemDecoration(dividerItemDecoration)

recyclerView.adapter = UserAdapter(getData())
recyclerView.layoutManager = LinearLayoutManager(this)
           

运行起来,效果如下:

Android RecyclerView ItemDecoration 分割线

如果我们把右边也设置一个偏移,代码如下:

/**
 * @author yanjun.zhao
 * @time 2020/12/21 2:57 PM
 * @desc 自定义 ItemDecoration
 */
class MyListItemDecoration 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.right = 10;    //右边偏移
    }
}
           

效果如下:

Android RecyclerView ItemDecoration 分割线

可以看到 recyclerView 的每一个item 底部、右边都有一个偏移。

如果最后一个 item 不绘制偏移量,怎么处理:

/**
 * @author yanjun.zhao
 * @time 2020/12/21 2:57 PM
 * @desc 自定义 ItemDecoration
 */
class MyListItemDecoration 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);

        int childPosition = parent.getChildAdapterPosition(view);
        int itemCount = parent.getAdapter().getItemCount();

        //最后一个item 不绘制
        if (childPosition != itemCount - 1) {
            outRect.bottom = 10;  //底部偏移
            outRect.right = 10;    //右边偏移
        }
    }
}
           
Android RecyclerView ItemDecoration 分割线

在官方的开发文档中有指出,

onDraw

是在

itemview

绘制之前,

onDrawOver

itemview

绘制之后。

可以看看下面的部分源码:

/**
     * RecyclerView的draw方法
     * @param c
     */
    @Override
    public void draw(Canvas c) {
        // 调用父类也就是View的draw方法
        super.draw(c);
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            // 执行ItemDecorations的onDrawOver方法
            mItemDecorations.get(i).onDrawOver(c, this, mState);
        }
    }

    /**
     *  View的draw方法
     * @param canvas
     */
    @CallSuper
    public void draw(Canvas canvas) {
        ....
        // View会继续调用onDraw
        if (!dirtyOpaque) onDraw(canvas);
        ....
    }

    /**
     * RecyclerView的onDraw方法
     * @param c
     */
    @Override
    public void onDraw(Canvas c) {
        super.onDraw(c);
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            // 执行ItemDecorations的onDraw方法
            mItemDecorations.get(i).onDraw(c, this, mState);
        }
    }

           

Android RecyclerView ItemDecoration 分割线

颜色值:

<resources>
    <!--半透明-->
    <color name="item_bg">#80000000</color>
</resources>
           

MyListItemDecoration 源码如下:

package com.cootek.recyclerviewdemo;

import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.Log;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

/**
 * @author yanjun.zhao
 * @time 2020/12/21 2:57 PM
 * @desc 自定义 ItemDecoration
 */
class MyListItemDecoration extends RecyclerView.ItemDecoration {

    private Paint mPaint;   //分割线画笔

    MyListItemDecoration() {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
    }

    @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 = 20;  //底部偏移量
        outRect.right = 10; //右边偏移量
        outRect.left = 10; //左边偏移量
    }

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

        //设置半透明颜色
        mPaint.setColor(parent.getResources().getColor(R.color.item_bg));


        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {

            if (i % 2 == 0) {
                continue;
            }

            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int top = child.getTop();
            final int bottom = top + params.height;
            final int left = child.getLeft();
            final int right = child.getWidth() + left;
            //在recyclerView item 上画一个半透明的蒙版
            c.drawRect(left, top, right, bottom, mPaint);
        }
    }

}

           
var dividerItemDecoration = MyListItemDecoration()
recyclerView.addItemDecoration(dividerItemDecoration)
recyclerView.adapter = UserAdapter(getData())
recyclerView.layoutManager = GridLayoutManager(this, 1)

//两列
findViewById<Button>(R.id.bt1).setOnClickListener {
       recyclerView.layoutManager = GridLayoutManager(this, 2)
}

//三列
findViewById<Button>(R.id.bt2).setOnClickListener {
       recyclerView.layoutManager = GridLayoutManager(this, 3)
}

//四列
findViewById<Button>(R.id.bt3).setOnClickListener {
      recyclerView.layoutManager = GridLayoutManager(this, 4)
}
           

废话不多说,线上效果图:

Android RecyclerView ItemDecoration 分割线
package com.cootek.recyclerviewdemo;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

/**
 * @author yanjun.zhao
 * @time 2020/12/21 2:57 PM
 * @desc 自定义 ItemDecoration
 */
class MyListItemDecoration extends RecyclerView.ItemDecoration {

    private Paint mPaint;   //分割线画笔
    private int lingHeight = 0;  //分割线高度

    MyListItemDecoration(Context context) {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.BLACK); //设置分割线颜色
        lingHeight = dpToPx(context, 1);
    }

    @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 = lingHeight;  //底部偏移量
    }

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

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int top = child.getBottom() + params.bottomMargin;
            final int bottom = top + lingHeight;
            final int left = child.getLeft();
            final int right = child.getWidth() + left;
            c.drawRect(left, top, right, bottom, mPaint);
        }
    }

    int dpToPx(Context context, int dps) {
        return Math.round(context.getResources().getDisplayMetrics().density * dps);
    }
}

           

先看一个 iphone 设置页面,请认真观察分割线,如下图:

Android RecyclerView ItemDecoration 分割线

我们注意到一个细节,分割线的宽度不是充满屏幕的,而是在左边有一个偏移量,这种效果怎么做呢?

package com.cootek.recyclerviewdemo;

import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.view.View;

import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;

/**
 * @author yanjun.zhao
 * @time 2020/12/21 2:57 PM
 * @desc 自定义 ItemDecoration
 */
class MyListItemDecoration extends RecyclerView.ItemDecoration {

    private Paint mPaint;   //分割线画笔
    private int lingHeight = 0;  //分割线高度
    private int lineMarginLeft = 0;

    MyListItemDecoration(Context context) {
        mPaint = new Paint();
        mPaint.setAntiAlias(true);
        mPaint.setColor(Color.BLACK); //设置分割线颜色
        lingHeight = dpToPx(context, 1);
        lineMarginLeft = dpToPx(context, 20);  //设置左侧偏移量
    }

    @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 = lingHeight;  //底部偏移量
    }

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

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child
                    .getLayoutParams();
            final int top = child.getBottom() + params.bottomMargin;
            final int bottom = top + lingHeight;
            final int left = child.getLeft() + lineMarginLeft;
            final int right = child.getWidth() + left - lineMarginLeft;
            c.drawRect(left, top, right, bottom, mPaint);
        }
    }

    int dpToPx(Context context, int dps) {
        return Math.round(context.getResources().getDisplayMetrics().density * dps);
    }
}