RecyclerView系列之一ItemDecoration

2024-02-28 07:38

本文主要是介绍RecyclerView系列之一ItemDecoration,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、添加分割线
1.1 引入ItemDecoration
在上一篇中,我们讲解了RecyclerView的基本使用方法,但有个问题:为什么Item之间没有分割线呢?其实,给RecyclerView添加分割线也非常简单,只需要添加上一句话:

DividerItemDecoration  mDivider = new DividerItemDecoration(this,DividerItemDecoration.VERTICAL);
mRecyclerView.addItemDecoration(mDivider);

完整的代码如下:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_linear);
    
    generateDatas();
    RecyclerView mRecyclerView = (RecyclerView) findViewById(R.id.linear_recycler_view);

    //线性布局
    LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
    linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
    mRecyclerView.setLayoutManager(linearLayoutManager);

    //初始化分隔线、添加分隔线
    DividerItemDecoration mDivider = new DividerItemDecoration(this, DividerItemDecoration.VERTICAL);
    mRecyclerView.addItemDecoration(mDivider);

    RecyclerAdapter adapter = new RecyclerAdapter(this, mDatas);
    mRecyclerView.setAdapter(adapter);
}

这里实现的效果如下图所示:

可以看到,这里只需要添加一句: mRecyclerView.addItemDecoration(mDivider);就可以给底部添加了一条横线,那什么是ItemDecoration呢?
首先,我们肯定理解什么是Item,在这个布局中,每个Item都单独占一行,在没加ItemDecoration时,下面红线框中就是一个Item:


1.2 什么是ItemDecoration
那ItemDecoration与Item是什么关系呢?对于英语来讲,Decoration是装饰的意思,ItemDecoration就是Item的装饰。在Item的四周,我们可以给它添加上自定义的装饰,比如刚才的横线,就是在底部给它添加一个横线的装饰。同样的,我们也可以在Item的上下左右各添加装饰,而且这些装饰是允许我们自定义的。系统只给我们提供了一个现成的Decoration类就是刚才使用的DividerItemDecoration,如果我们想实现其它的装饰效果,就需要自定义了。下面这些漂亮的效果都可以使用自定义ItemDecoration来实现:


2、 自定义ItemDecoration
2.1 getItemOffsets
2.1.1 getItemOffsets的意义
当我们要重写ItemDecoration时,主要涉及到三个函数:

public class LinearItemDecoration extends RecyclerView.ItemDecoration {
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
    }

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

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
    }
}

这三个函数我们会逐个讲解,首先,我们来看看getItemOffsets:
getItemOffsets的主要作用就是给item的四周加上边距,实现的效果类似于margin,将item的四周撑开一些距离,在撑开这些距离后,我们就可以利用上面的onDraw函数,在这个距离上进行绘图了。在了解了getItemOffsets的作用之后,我们来看看这个函数本身:
getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)

Rect outRect:这个是最难理解的部分,outRect就是表示在item的上下左右所撑开的距离,后面详细讲解。
View view:是指当前Item的View对象
RecyclerView parent: 是指RecyclerView 本身
RecyclerView.State state:通过State可以获取当前RecyclerView的状态,也可以通过State在RecyclerView各组件间传递参数,具体的文档,大家可以参考:https://developer.android.com/reference/android/support/v7/widget/RecyclerView.State
下面我们专门来看看Rect outRect这个参数,outRect 中的 top、left、right、bottom四个点,并不是普通意义的坐标点,而是指的在Item上、左、右、下各撑开的距离,这个值默认是0,示意图如下所示:


2.1.2 getItemOffsets示例
我们知道,想要实现分隔线,有一种方法是在Item的上方空出一像素的间隔,这样就会漏出底线,看起来就是分割线了。所以我们回到刚才的示例,去掉DividerItemDecoration,改为自定义的LinearItemDecoration。
首先,给整个Activity添加上一个红色的背景色:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#ff0000"
    tools:context=".LinearActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/linear_recycler_view"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    </android.support.v7.widget.RecyclerView>

</LinearLayout>

之后,给每个Item添加上默认的背景色白色,这样有白色的地方就不会透出背景色的红色了,而没有白色的地方就会露出红色:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:background="@android:color/white"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/item_tv"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="10dp" />

</LinearLayout>

然后就是自定义LinearItemDecoration :

public class LinearItemDecoration extends RecyclerView.ItemDecoration {
    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
    }

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

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.top=1;
    }
}

在这里,我们将item上方面所撑开的距离硬编码为1px;

最后,将LinearItemDecoration添加进RecyclerView:

public class LinearActivity extends AppCompatActivity {
    private ArrayList<String> mDatas = new ArrayList<>();

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_linear);

        generateDatas();
        RecyclerView mRecyclerView = (RecyclerView) findViewById(R.id.linear_recycler_view);
        …………
        //添加分隔线
        mRecyclerView.addItemDecoration(new LinearItemDecoration());

        RecyclerAdapter adapter = new RecyclerAdapter(this, mDatas);
        mRecyclerView.setAdapter(adapter);
    }
    //其它代码,参考源码
    …………
}

效果如下图所示:

可以看到每个Item的上方都出现了一条红线。尤其从第一个Item可以看出来。
同样的,如果我们改为底部1px,左侧50px,右侧100px:

public class LinearItemDecoration extends RecyclerView.ItemDecoration {
    …………
    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.left=50;
        outRect.right=100;
        outRect.bottom=1;
    }
}

看下效果:

从第一个Item可以看出,顶部是没有红线的,因为我们没有设置outRect.top,所以它默认是0,因为outRect.right=100,而outRect.left=50,明显可以看出右侧的红色宽度是左侧的两倍。

2.2 onDraw
2.2.1 onDraw的用法
在理解了getItemOffsets的用法以后,我们再来看看onDraw函数:

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

onDraw函数有三个参数,RecyclerView parent, RecyclerView.State state的意义与getItemOffsets的相同。而

Canvas c: 是指通过getItemOffsets撑开的空白区域所对应的画布,通过这个canvas对象,可以在getItemOffsets所撑出来的区域任意绘图。
那这个就厉害了,我们知道Canvas是有非常丰富的绘图函数的,那我们先来个简单的,通过getItemOffsets将Item的左侧撑出来150的距离,然后在中间画一个圆形:

public class LinearItemDecoration extends RecyclerView.ItemDecoration {
    private Paint mPaint;
    public LinearItemDecoration(){
        mPaint = new Paint();
        mPaint.setColor(Color.GREEN);
    }
    @Override
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDraw(c, parent, state);
        int childCount = parent.getChildCount();

        for (int i=0;i<childCount;i++){
            View child = parent.getChildAt(i);
            int cx = 100;
            int cy = child.getTop()+child.getHeight()/2;
            c.drawCircle(cx,cy,20,mPaint);
        }
}

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

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.left=200;
        outRect.bottom=1;
    }
}

首先,在getItemOffsets中,将左侧撑出200px的距离,同样底部留出1px的空间以显示底部分割线。
然后在onDraw中,在每个Item的左侧中间画上半径为20的绿色圆形。

效果如下图所示:


需要注意的是,getItemOffsets是针对每个Item都会走一次,也就是说每个Item的outRect都可以不同,但是onDraw和onDrawOver所整个ItemDecoration只执行一次的,并不是针对Item的,所以我们需要在onDraw和onDrawOver中绘图时,一次性将所有Item的ItemDecoration绘制完成。从上面也可以看出,这里在onDraw函数中绘图时,通过for循环对每一个item画上一个绿色圆。

拓展:获取outRect的各个值
在上面的例子中,我们onDraw中使用到outRect的值时,都是直接使用的数字硬编码,比如在outRect是我们将左侧撑开的距离设置为200,所以画圆的中心点的X坐标就是100,所以在onDraw函数中直接使用了int cx = 100;很明显,在实际工作中要严格避免类似的硬编码,因为硬编码会使代码变得极其难以维护。那我们怎么在代码中获取到getItemOffsets中所设置的各个item的outRect的值呢?
可以通过LayoutManager来获取,方法如下:其中parent是指RecylerView本身,而child是指RecyclerView的Item的View对象

RecyclerView.LayoutManager manager = parent.getLayoutManager();
int left = manager.getLeftDecorationWidth(child);
int top = manager.getTopDecorationHeight(child);
int right = manager.getRightDecorationWidth(child);
int bottom = manager.getBottomDecorationHeight(child);

所以我们上面在onDraw函数中的硬编码,就可以用下面的动态获取代码来代替:

public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDraw(c, parent, state);
        int childCount = parent.getChildCount();
        RecyclerView.LayoutManager manager = parent.getLayoutManager();
        for (int i=0;i<childCount;i++){
            View child = parent.getChildAt(i);
            //动态获取outRect的left值
            int left = manager.getLeftDecorationWidth(child);
            int cx = left/2;
            int cy = child.getTop()+child.getHeight()/2;
            c.drawCircle(cx,cy,20,mPaint);
        }
}

到这,大家实现开头讲解的这个效果应该不难了:

该工程源码地址:https://github.com/vipulasri/Timeline-View ,他是通过自定义View来实现的,大家也可以尝试通过RecyclerView的ItemDecoration来实现出来。

源码在文章底部给出
2.2.2 onDraw的问题
如果我们在将上面画圆的例子修改下,将画圆改为绘制一个图片:

代码如下:

public class LinearItemDecoration extends RecyclerView.ItemDecoration {
    private Paint mPaint;
    private Bitmap mBmp;

    public LinearItemDecoration(Context context) {
        mPaint = new Paint();
        mPaint.setColor(Color.GREEN);
        BitmapFactory.Options options = new BitmapFactory.Options();
        options.inSampleSize = 2;
        mBmp = BitmapFactory.decodeResource(context.getResources(),R.mipmap.icon,options);
    }

    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            View child = parent.getChildAt(i);
            c.drawBitmap(mBmp,0,child.getTop(), mPaint);
        }
    }

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

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        super.getItemOffsets(outRect, view, parent, state);
        outRect.left = 200;
        outRect.bottom = 1;
    }
}

首先,因为图片比较大,在LinearItemDecoration初始化的时候,通过options.inSampleSize参数将图片缩放小为原大小的1/2;

public LinearItemDecoration(Context context) {
    …………
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inSampleSize = 2;
    mBmp = BitmapFactory.decodeResource(context.getResources(),R.mipmap.icon,options);
}

同样,在getItemOffsets中,将左侧边距设置为200,底部预留一像素显示分割线。
最后,在onDraw中,将图形在每个Item的左上角显示出来。
效果如下图所示:

如果我们把bitmap缩放去掉:

    public LinearItemDecoration(Context context) {
        BitmapFactory.Options options = new BitmapFactory.Options();
        //options.inSampleSize = 2;
        mBmp = BitmapFactory.decodeResource(context.getResources(),R.mipmap.icon,options);
    }

效果图如下:

 

可以看到当图片过大时,在超出getItemOffsets函数所设定的outRect范围的部分将是不可见的。这是因为在整个绘制流程中,是选调用ItemDecoration的onDraw函数,然后再调用Item的onDraw函数,最后调用ItemDecoration的onDrawOver函数。
所以在ItemDecoration的onDraw函数中绘制的内容,当超出边界时,会被Item所覆盖。但是因为最后才调用ItemDecoration的OnDrawOver函数,所以在onDrawOver中绘制的内容就不受outRect边界的限制,可以覆盖Item的区域显示。

源码在文章底部给出
2.3 onDrawOver
上面我们已经提到,ItemDecoration与Item的绘制顺序为:decoration 的 onDraw->item的 onDraw->decoration 的 onDrawOver,这三者是依次发生的。
所以,onDrawOver 是绘制在最上层的,所以它的绘制位置并不受限制(当然,decoration 的 onDraw 绘制范围也不受限制,只不过不可见,被item所覆盖),所以利用 onDrawOver 可以做很多事情,例如为 RecyclerView 整体顶部绘制一个蒙层、超出itemDecoration的范围绘制图像。
比如,我们实现下面这样的效果:

 

在这个效果中,我们在最顶部绘制了一个渐变蒙版,而且每隔五个item绘制一个勋章。动图效果是这样的:

可见这个蒙层是一直显示在顶部的。下面我们就来看看具体 实现吧。
1、添加图片
第一步,当然是将勋章图片(xunzhang.png)加入res/mipmap文件夹中或者res/drawable文件夹中。

2、初始化
然后在LinearItemDecoration初始化时,将图片转为bitmap对象:

public class LinearItemDecoration extends RecyclerView.ItemDecoration {
    private Paint mPaint;
    private Bitmap mMedalBmp;

    public LinearItemDecoration(Context context) {
        mPaint = new Paint();
        mPaint.setColor(Color.GREEN);
        BitmapFactory.Options options = new BitmapFactory.Options();
        mMedalBmp = BitmapFactory.decodeResource(context.getResources(), R.mipmap.xunzhang);
    }
}

3、绘制勋章
在onDrawOver中将勋章每隔五个item绘制出来

public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDrawOver(c, parent, state);
    //画勋章
    RecyclerView.LayoutManager manager = parent.getLayoutManager();
    int childCount = parent.getChildCount();
    for (int i = 0; i < childCount; i++) {
        View child = parent.getChildAt(i);
        int index = parent.getChildAdapterPosition(child);
        int left = manager.getLeftDecorationWidth(child);
        if (index % 5 == 0) {
            c.drawBitmap(mMedalBmp, left - mMedalBmp.getWidth() / 2, child.getTop(), mPaint);
        }
    }
}

绘制勋章的时候,需要注意,我们需要将勋章的中间位置绘制item与decoration的交界处,所以它的X坐标就是left - mMedalBmp.getWidth() / 2
4、绘制渐变蒙版
因为蒙版同样是浮在item之上的,所以我们同样是在onDrawOver中绘制,在绘制勋章之后,绘制蒙版:

public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDrawOver(c, parent, state);
    //画勋章
    …………
    //画蒙版
    View temp = parent.getChildAt(0);
    LinearGradient gradient = new LinearGradient(parent.getWidth() / 2, 0, parent.getWidth() / 2, temp.getHeight() * 3,
            0xff0000ff, 0x000000ff, Shader.TileMode.CLAMP);
    mPaint.setShader(gradient);
    c.drawRect(0, 0, parent.getWidth(), temp.getHeight() * 3, mPaint);
}

首先,创建一个LinearGradient对象,让它从蓝色不透明到蓝色全透明渐变。然后绘制出来。
 

这篇关于RecyclerView系列之一ItemDecoration的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/754819

相关文章

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

科研绘图系列:R语言扩展物种堆积图(Extended Stacked Barplot)

介绍 R语言的扩展物种堆积图是一种数据可视化工具,它不仅展示了物种的堆积结果,还整合了不同样本分组之间的差异性分析结果。这种图形表示方法能够直观地比较不同物种在各个分组中的显著性差异,为研究者提供了一种有效的数据解读方式。 加载R包 knitr::opts_chunk$set(warning = F, message = F)library(tidyverse)library(phyl

【生成模型系列(初级)】嵌入(Embedding)方程——自然语言处理的数学灵魂【通俗理解】

【通俗理解】嵌入(Embedding)方程——自然语言处理的数学灵魂 关键词提炼 #嵌入方程 #自然语言处理 #词向量 #机器学习 #神经网络 #向量空间模型 #Siri #Google翻译 #AlexNet 第一节:嵌入方程的类比与核心概念【尽可能通俗】 嵌入方程可以被看作是自然语言处理中的“翻译机”,它将文本中的单词或短语转换成计算机能够理解的数学形式,即向量。 正如翻译机将一种语言

flume系列之:查看flume系统日志、查看统计flume日志类型、查看flume日志

遍历指定目录下多个文件查找指定内容 服务器系统日志会记录flume相关日志 cat /var/log/messages |grep -i oom 查找系统日志中关于flume的指定日志 import osdef search_string_in_files(directory, search_string):count = 0

GPT系列之:GPT-1,GPT-2,GPT-3详细解读

一、GPT1 论文:Improving Language Understanding by Generative Pre-Training 链接:https://cdn.openai.com/research-covers/languageunsupervised/language_understanding_paper.pdf 启发点:生成loss和微调loss同时作用,让下游任务来适应预训

Java基础回顾系列-第七天-高级编程之IO

Java基础回顾系列-第七天-高级编程之IO 文件操作字节流与字符流OutputStream字节输出流FileOutputStream InputStream字节输入流FileInputStream Writer字符输出流FileWriter Reader字符输入流字节流与字符流的区别转换流InputStreamReaderOutputStreamWriter 文件复制 字符编码内存操作流(

Java基础回顾系列-第五天-高级编程之API类库

Java基础回顾系列-第五天-高级编程之API类库 Java基础类库StringBufferStringBuilderStringCharSequence接口AutoCloseable接口RuntimeSystemCleaner对象克隆 数字操作类Math数学计算类Random随机数生成类BigInteger/BigDecimal大数字操作类 日期操作类DateSimpleDateForma

Java基础回顾系列-第三天-Lambda表达式

Java基础回顾系列-第三天-Lambda表达式 Lambda表达式方法引用引用静态方法引用实例化对象的方法引用特定类型的方法引用构造方法 内建函数式接口Function基础接口DoubleToIntFunction 类型转换接口Consumer消费型函数式接口Supplier供给型函数式接口Predicate断言型函数式接口 Stream API 该篇博文需重点了解:内建函数式

Java基础回顾系列-第二天-面向对象编程

面向对象编程 Java类核心开发结构面向对象封装继承多态 抽象类abstract接口interface抽象类与接口的区别深入分析类与对象内存分析 继承extends重写(Override)与重载(Overload)重写(Override)重载(Overload)重写与重载之间的区别总结 this关键字static关键字static变量static方法static代码块 代码块String类特

Java基础回顾系列-第六天-Java集合

Java基础回顾系列-第六天-Java集合 集合概述数组的弊端集合框架的优点Java集合关系图集合框架体系图java.util.Collection接口 List集合java.util.List接口java.util.ArrayListjava.util.LinkedListjava.util.Vector Set集合java.util.Set接口java.util.HashSetjava