RecyclerView 知识梳理(4) - ItemDecoration

一、概述

通过ItemDecoration,可以给RecyclerView或者RecyclerView中的每个Item添加额外的装饰效果,最常用的就是用来为Item之间添加分割线,今天,我们就来一起学习有关的知识:

  • API
  • DividerItemDecoration解析
  • 自定义ItemDecoration

二、API介绍

当我们实现自己的ItemDecoration时,需要继承于ItemDecoration,并根据需要实现以下三个方法:

2.1 public void onDraw(Canvas c, RecyclerView parent, State state)

  • canvasRecyclerViewcanvas
  • parentRecyclerView实例
  • StateRecyclerView当前的状态,值包括START/LAYOUT/ANIMATION

所有在这个方法中的绘制操作,将会在itemViews被绘制之前执行,因此,它会显示在itemView之下。

2.2 public void onDrawOver(Canvas c, RecyclerView parent, State state)

2.1方法类似,区别在于它绘制在itemViews之上。

2.3 public void getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)

通过outRect,可以设置item之间的间隔,间隔区域的大小就是outRect所指定的范围,view就是对应位置的itemView,其它的参数解释和上面相同。

三、DividerItemDecoration解析

3.1 使用方法

上面我们解释了需要重写的方法以及其中参数的含义,下面,我们通过官方自带的DividerItemDecoration,来进一步加深对这些方法的认识。
DividerItemDecoration是为LinearLayoutManager提供的分割线,在创建它的时候,需要指定ORIENTATION,这个方向应当和LinearLayoutManager的方向相同。

    private void init() {
        RecyclerView recyclerView = (RecyclerView) findViewById(R.id.rv_content);
        mTitles = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            mTitles.add(String.valueOf(i));
        }
        BaseAdapter baseAdapter = new BaseAdapter(mTitles);
        recyclerView.setLayoutManager(new LinearLayoutManager(this));
        recyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
        recyclerView.setAdapter(baseAdapter);
    }

最终展示的效果为:

3.2 源码解析

3.2.1 绘制

DividerItemDecoration重写了基类当中的onDraw方法,也就是说这个分割线是在itemView之前绘制的:

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        if (parent.getLayoutManager() == null) {
            return;
        }
        if (mOrientation == VERTICAL) {
            drawVertical(c, parent);
        } else {
            drawHorizontal(c, parent);
        }
    }

我们先看纵向排列的RecyclerView分割线:

    @SuppressLint("NewApi")
    private void drawVertical(Canvas canvas, RecyclerView parent) {
        //首先保存画布
        canvas.save();
        final int left;
        final int right;
        //确定左右边界的范围,如果RecyclerView不允许子View绘制在Padding内,那么这个范围为去掉Padding后的范围
        if (parent.getClipToPadding()) {
            left = parent.getPaddingLeft();
            right = parent.getWidth() - parent.getPaddingRight();
            canvas.clipRect(left, parent.getPaddingTop(), right,
                    parent.getHeight() - parent.getPaddingBottom());
        } else {
            left = 0;
            right = parent.getWidth();
        }

        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View child = parent.getChildAt(i);
            //获得itemView的范围,这个范围包括了margin和offset,它们被保存在mBounds当中
            parent.getDecoratedBoundsWithMargins(child, mBounds);
            //需要考虑translationY和translationY
            final int bottom = mBounds.bottom + Math.round(ViewCompat.getTranslationY(child));
            //由于是垂直排列的,因此上边界等于下边界减去分割线的高度.
            final int top = bottom - mDivider.getIntrinsicHeight();
            //设置divider和范围
            mDivider.setBounds(left, top, right, bottom);
            //绘制.
            mDivider.draw(canvas);
        }
        //回复画布.
        canvas.restore();
    }

整个过程分为三步:

  • 确定子ViewRecyclerView中的绘制范围
  • 确定每个子View的范围
  • 确定mDivider的绘制范围

下图就是最终计算的结果:

横向排列的RecyclerView列表和上面的原理是相同的,区别就在于计算mDivider.setBounds的计算:

//....
parent.getLayoutManager().getDecoratedBoundsWithMargins(child, mBounds);
final int right = mBounds.right + Math.round(ViewCompat.getTranslationX(child));
final int left = right - mDivider.getIntrinsicWidth();
mDivider.setBounds(left, top, right, bottom);
//..

3.2.2 边界处理

从上面的分析可以知道,如果将divider直接绘制在itemView的范围内,那么由于我们是先绘制divider,再绘制itemView的内容的,那么它就会被覆盖,因此,通过重写getItemOffsets,通过其中的outRect来指定留出的空隙:

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
            RecyclerView.State state) {
        if (mOrientation == VERTICAL) {
            //如果是纵向排列,那么要在itemView的下方留出一个下边界
            outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
        } else {
            //如果是横向排列,那么要在itemView的右方留出一个右边界
            outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
        }
    }

四、自定义ItemDecoration

下面,我们参考上面的写法,写一个简单的GridLayoutManager的分割线:

public class GridDividerItemDecoration extends RecyclerView.ItemDecoration {

    private static final int[] ATTRS = new int[] { android.R.attr.listDivider };
    private Drawable mDivider;
    private final Rect mBounds = new Rect();

    public GridDividerItemDecoration(Context context) {
        final TypedArray a = context.obtainStyledAttributes(ATTRS);
        mDivider = a.getDrawable(0);
        a.recycle();
    }

    public void setDrawable(@NonNull Drawable drawable) {
        mDivider = drawable;
    }

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

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        outRect.set(0, 0, mDivider.getIntrinsicWidth(), mDivider.getIntrinsicHeight());
    }

    private void drawDivider(Canvas canvas, RecyclerView parent) {
        canvas.save();
        final int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            final View view = parent.getChildAt(i);
            parent.getDecoratedBoundsWithMargins(view, mBounds);
            mDivider.setBounds(mBounds.right - mDivider.getIntrinsicWidth(), mBounds.top, mBounds.right, mBounds.bottom);
            mDivider.draw(canvas);
            mDivider.setBounds(mBounds.left, mBounds.bottom - mDivider.getIntrinsicHeight(), mBounds.right , mBounds.bottom);
            mDivider.draw(canvas);
        }
        canvas.restore();
   }
}

最终的效果为:

这里我们为了演示方便,没有考虑最后一列或者最后一行没有分割线的情况,这篇文章写的比较好:Android RecyclerView 使用完全解析 体验艺术般的控件

五、总结

ItemDecoration的使用并不难,大多数情况下就只需要重写onDrawonDrawOver中的一个;如果需要在Item之间添加间隔,那么要重写getItemOffsets并理解outRect的含义,假如不需要添加间隔,那么不需要重写该方法。

时间: 2024-09-30 09:16:40

RecyclerView 知识梳理(4) - ItemDecoration的相关文章

RecyclerView 知识梳理(5) - ItemTouchHelper

一.概述 ItemTouchHelper在RecyclerView的整个体系中,负责监听Item的手势操作,我们通过给它设置一个继承于ItemTouchHelper.Callback的子类,在其中处理Item的UI变化,就可以完成侧滑删除.拖动排序等操作,下面,我们分以下几部介绍: API解析 实战 采用默认动画 自定义侧滑删除动画 二.API分析 对于Item的手势操作分为两种:侧滑和拖动,如果需要支持这两种,那么需要给ItemTouchHelper传入一个ItemTouchHelper.Ca

RecyclerView 知识梳理(2) - Adapter

一.概述 当我们使用RecyclerView时,第一件事就是要继承于RecyclerView.Adapter,实现其中的抽象方法,来处理数据的展示逻辑,今天,我们就来介绍一下Adapter中的相关方法. 二.基础用法 我们从一个简单的线性列表布局开始,介绍RecyclerView.Adapter的基础用法.首先,需要导入远程依赖包: compile'com.android.support:recyclerview-v7:25.3.1' 接着,继承于RecyclerView.Adapter来实现自

指针知识梳理7- 函数指针

一.函数的地址 前面讲 程序运行起来以后,在内存中有代码区,程序运行每一条指令,是从内存中读出来这条指令,然后再运行. 所谓函数的地址是指函数的入口地址,这个函数的从这个地址開始进入运行,也就是从这个地址处取指令运行. 那么在代码层面,函数的地址用 函数指针变量 来存储. 二.基本使用 1.函数指针定义 函数指针的定义,在语法看起来略微有点怪,仅仅须要记住形式 返回值 (*指针变量名)(形參类型): 比方,下面4个函数 void func1(void) { } int func2(void) {

[SQL] SQL 基础知识梳理(一)- 数据库与 SQL

SQL 基础知识梳理(一)- 数据库与 SQL [博主]反骨仔 [原文地址]http://www.cnblogs.com/liqingwen/p/5902856.html 序 目录 What's 数据库 数据库结构 SQL 概要 创建表 删除和更新表 1-1 What's 数据库 1.数据库(Database,DB):将大量数据保存起来,通过计算机加工而成的可以进行高效访问的数据集合.如:大型-银行存储的信息,小型-电话簿. 2.数据库管理系统(Batabase Management Syste

JavaScript基础知识梳理--数组

JavaScript基础知识梳理--数组 1.创建方法 空数组:var obj=new Array(); 指定长度数组: var obj=new Array( size ); 指定元素数组 :  var obj=new Array( 元素1,元素2,....): 单位数组:var obj=new Array[ 元素1,元素2,元素3,...,元素N]; 多维数组:var a=new Array( [数组1],[数组2],[数组3],...,[数组N] ); 2.基本操作 存取数组元素: 单维数组

Java基础知识梳理《一》

一.Java数据类型(简单称之为“四类八种”) java 基本的数据类型长度都是固定的,好处是在实现跨平台时就统一了. 1.整型 byte short int long (分别是1,2,4,8个字节) 类型 存储需求 位数 取值范围 byte 1字节 8位 -128~127 short 2字节 16位 -2^15 ~2^15-1 int 4字节 32位 -2^31~2^31-1 long 8字节 64位 -2^63~2^63-1 当超出int表示范围时,应该使用long型,添加后缀一大写的L 注

struts2 知识梳理

一:struts.xml配置详解: 1.<include> 表示引入其他配置文件 2.<constant> 定义常量 3.<package>:  属性 是否必需 描述name 是 包名,作为其它包应用本包的标记extends 否 设置本包继承其它包namespace 否 设置包的命名空间,会改变url,abstact 否 设置为抽象包 4<action>和<result> <action>有name,class,method,conv

指针知识梳理6-const与指针

const 定义的变量为只读变量,在语法层面上通过这个变量去修改内存是不允许的. 但是对于以下代码,就有很多人绕了: const int  *p1;  //p1能变,*p1不能变 int const  *p2;  //p2能变,*p2不能变 int *const  p3;  //p3不能变,*p2能变 我们通过代码来验证说明这三种写法: </pre><p></p><pre> #include <stdio.h> int main() { int

[SQL] SQL 基础知识梳理(四) - 数据更新

SQL 基础知识梳理(四) - 数据更新 [博主]反骨仔 [原文]http://www.cnblogs.com/liqingwen/p/5929786.html 目录 一.插入数据 1.INSERT 语句的基本语法 --语法: --INSERT INTO <表名>(列1, 列2, ...) VALUES (值1, 值2, ...) INSERT INTO dbo.Shohin ( shohin_id , shohin_mei , shohin_bunrui , hanbai_tanka , s