RecyclerView之ItemDecoration讲解及高级特性实践

RecyclerView之ItemDecoration讲解及高级特性实践

今日科技快讯

近日有消息称:阿里巴巴集团与其关联公司蚂蚁金服,计划牵头对订餐平台饿了么投入至少10亿美元资金。因为随着越来越多的中国消费者开始使用智能手机订餐、预约家政服务,此类服务的市场规模在今年将有望达到人民币7.28万亿元。

作者简介

本篇来自 frank909 的投稿,分享了ItemDecoration的基本用法以及进阶使用,希望能够帮助到大家。另外,大家不要忘了明天还要继续上班。RecyclerView之ItemDecoration讲解及高级特性实践

frank909 的博客地址:

http://blog.csdn.net/briblue

前言

毫无疑问,RecyclerView 是现在 Android 世界中最重要的系统组件之一,它的出现就是为了高效代替 ListView 和 GridView。当时它的出现解决了我一个大的需求,这个需求就是在电视盒子界面上横向加载应用列表,由于 ListView 没有横向加载的功能,而网络上开源的那些 HorizontalListView 又不满足需求,所以我们只能自定义 ViewGroup 来实现需求,但是回收机制不是很完善,所以性能并不好,所以当 RecyclerView 横空出世时,我第一时间拥抱了它,并推荐 Android 开发小组成员们去了解它。

但后来,我发现 RecyclerView 除了比 ListView 好用外,某些地方它却更复杂了,它将更多的权力交给了开发者自己,比如布局,比如 ITEM 的分割线,比如点击监听等等。但总归它是好东西,所以我们得多花些时间来学习,平常开发我们一般按照 RecyclerView 的基本用法便可以实现绝大多数需求,但是某些场景下却远远不够,比如我们不想局限于 LinearLayoutManager 想自己定义 LayoutManager,我们需要定义时光轴的效果,我们想实现美妙的添加删除动画等等,这些情况下解决问题的话需要我们对 RecyclerView 本身有足够的了解。

今天,这篇文章不讲 RecyclerView 基本的知识和用法,讲它一个有趣的知识点 ItemDecoration。

ItemDecoration

Decoration 的英文意思是装饰物的意思,引申到这里来,肯定也是与 RecyclerView 的界面装饰有关。我们常见的就是分割线了。

我们在使用 ListView 的时候只要在 xml 文件中,使用 android:divider 就可以,但是很遗憾 RecyclerView 却没有相应的控制。

我们新建一个工程,然后在一个页面里面添加一个 RecyclerView。创建相关的 Adapter,加载布局文件,这里布局文件很简单,就是一个 TextView,再之后在 Activity 初始化它。

RecyclerView之ItemDecoration讲解及高级特性实践

RecyclerView之ItemDecoration讲解及高级特性实践

可以看到所有的选项都混在一起,为了美观应该需要 1px 的分割线,之前我一般在 Item 的布局文件中设置它的 topMargin 或者是 bottomMargin,所以我们可以在相关的 Adapter 中这样修改。

RecyclerView之ItemDecoration讲解及高级特性实践

效果如下:

RecyclerView之ItemDecoration讲解及高级特性实践

现在我们同样可以通过给 RecyclerView 添加 ItemDecoration 来实现它。

首先,我们需要自定义一个 ItemDecoration,按照目前的需求,我们只需要实现它的一个方法就可以了。

RecyclerView之ItemDecoration讲解及高级特性实践

然后在 Activity 中添加它到 RecyclerView 就可以了。

RecyclerView之ItemDecoration讲解及高级特性实践

效果跟上面是一样的。

getItemOffsets()

我们可以看到自定义的 TestDividerItemDeoration 只实现了一个方法 getItemOffsets()。方法里面有四个参数。

  • Rect outRect

  • View view

  • RecyclerView parent

  • RecyclerView.State state

这四个参数分别干什么的呢?我们不妨在 AndroidStudio 中按 Ctrl 键点击方法名,就可以到了它被调用的位置。

RecyclerView之ItemDecoration讲解及高级特性实践

我们注意这一行代码 mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState); 很容易得知,outRect 是一个全为 0 的 Rect。view 指 RecyclerView 中的 Item。parent 就是 RecyclerView 本身,state 就是一个状态。

我们可以看下面的这张图。

RecyclerView之ItemDecoration讲解及高级特性实践

绿色区域代表 RecyclerView 中的一个 ItemView,而外面橙色区域也就是相应的 outRect,也就是 ItemView 与其它组件的偏移区域,等同于 margin 属性,通过复写 getItemOffsets() 方法,然后指定 outRect 中的 top、left、right、bottom 就可以控制各个方向的间隔了。注意的是这些属性都是偏移量,是指偏移 ItemView 各个方向的数值。

在上面的例子中我设置了 outRect.top = 1; 所以每个 ItemView 之间有 1 px 的空隙,而这 1 px 空隙透露了下面背景色,所以看起来就像是分隔线,这实现了简单的分隔线效果,但这种方法分隔线的效果只能取决于背景色,如果我要定制分割线的颜色呢?这个时候就要讲到一个新的方法名 onDraw()。

onDraw()

在 Android 中的每一个 View 中 onDraw() 是很重要的一个方法,用来绘制组件的UI效果,所以在 ItemDecocration 中它自然也是用来绘制外观的。我们来看它的方法声明。

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

可以看到它传递了一个 Canvas 参数对象,所以它拥有了绘制的能力。但是怎么绘制呢?

其实它是配合了前面的 getItemOffsets 方法一起使用的,getItemOffsets 撑开了 ItemView 的上下左右间隔区域,而 onDraw 方法通过计算每个 ItemView 的坐标位置与它的 outRect 值来确定它要绘制内容的区间。

假设,我们要设计一个高度为 2 px 的红色分割线,那么我们就需要在每个 ItemView top位置上方画一个 2 px 高度的矩形,然后填充颜色为红色。

需要注意的一点是 getItemOffsets 是针对每一个 ItemView,而 onDraw 方法却是针对 RecyclerView 本身,所以在 onDraw 方法中需要遍历屏幕上可见的 ItemView,分别获取它们的位置信息,然后分别的绘制对应的分割线。

我们看下面的这张示意图:

RecyclerView之ItemDecoration讲解及高级特性实践

为了便于观察我将第一条分割线的颜色透明化了,我们可以看到每条分割线绘制的区域其实就是 outRect.top 至 ItemView.top 之间的区域,所以我们就需要在当初 getOffsets 方法进行位置偏移时就记录下每个 itemView 向上的间隔距离,之后的逻辑就是遍历屏幕上的 View,然后描绘分割线。

RecyclerView之ItemDecoration讲解及高级特性实践

然后我们在 Activity 将 ColorDividerItemDecoration 添加到相应的 RecyclerView 中就可以了。

mRecyclerView.addItemDecoration(new ColorDividerItemDecoration());

RecyclerView之ItemDecoration讲解及高级特性实践

至此,红色的分割线就搞定了。

但一定要注意的是,onDraw 方法可不只能绘制简单的线条,它可是拥有 Canvas 的,所以画圆、画矩形、画弧形、绘制图片都不在话下。为了提高本篇代码的技术含量,下面我们通过 ItemDecoration 来实现一个时光轴的效果。

通过ItemDecoration实现时光轴的效果

编码的开始先做设计,或者说先思考。思考我们要做什么,或者说要怎么做。

RecyclerView之ItemDecoration讲解及高级特性实践

我们可以看到左边白色的图案就大概是我们时光轴要绘制的图形。我们通过 getItemOffsets 方法来对 ItemView 进行 left 和 top 的间距设置。然后确定好轴线的起始坐标,中间轴结点的图形或者是图案。我们可以通过 ItemView 将相应的时光轴片断分解,如下图。

RecyclerView之ItemDecoration讲解及高级特性实践

主要是一些参数的确定,例如 DividerHeight,注意这个 DividerHeight 不是指 ItemView 向上的间隔值,而是相应的 ItemDecoration 的高度。中心坐标 (centerX,centerY),还有上下两段轴线的起始坐标。有了这些参数后,我们就能轻松地编码了。

RecyclerView之ItemDecoration讲解及高级特性实践

RecyclerView之ItemDecoration讲解及高级特性实践

感觉不怎么美观,我们尝试将结点的实心圆改成空心圆。

RecyclerView之ItemDecoration讲解及高级特性实践

RecyclerView之ItemDecoration讲解及高级特性实践

感觉美观了许多。同时,我们可以将轴结点用图标代替圆或者圆圈。 

RecyclerView之ItemDecoration讲解及高级特性实践

上面的图标感觉更好看了。

需要注意的是 onDraw 方法,ItemDecoration 是在 ItemView 的下方绘制的,也就是 ItemView 可能会覆盖 ItemDecoration 的内容。我们可以验证一下,在时间轴与 ItemView 的边界画一个完整的圆,观察它的效果。

mPaint.setStyle(Paint.Style.STROKE);
c.drawCircle(view.getLeft(),centerY,mNodeRadius,mPaint);
mPaint.setStyle(Paint.Style.FILL_AND_STROKE);

RecyclerView之ItemDecoration讲解及高级特性实践

可以看到,在重合的地方,圆圈确实被 ItemView 内容覆盖了。

大家可能会想到,ItemDecoration 内容能不能覆盖在 ItemView 内容之上呢?

答案是肯定的,但不是在 onDraw() 方法实现,而是另外一个方法 onDrawOver()。

onDrawOver和角标

现实中的APP或者网站经常有一些排行榜比如下面:

RecyclerView之ItemDecoration讲解及高级特性实践

或者这样:

RecyclerView之ItemDecoration讲解及高级特性实践

这些角标都是绘制在 ItemView 之上的,现在有了 ItemDecoration 我们也可以轻松而优雅地实现它。

比如我们要实现一个图书销量排行榜。我们有大概的草图。

RecyclerView之ItemDecoration讲解及高级特性实践

然后我们就可以编码了,这里我们主要看下自定义 FlagItemDecoration

RecyclerView之ItemDecoration讲解及高级特性实践

最终效果如下图:

RecyclerView之ItemDecoration讲解及高级特性实践

有人在想,通过 ItemView 中的布局文件不就可以完成这样的操作吗?是的,确实是可以的,将 Flag 角标定义在每一个 ItemView 布局文件中,然后在 Adapter 的 onBindViewHolder 方法中根据 postion 的值来决定是否加载角标。

但是这里是为了说明 ItemDecoration 中的 onDrawOver 方法,为了说明它确实能让 ItemDecoration 图像绘制在 ItemView 内容之上。事实上,ItemDecoration 的妙处还有好多好多。

总结

自定义一个 ItemDecoration 通常要根据需要,复写它的 3 个方法。

  • getItemOffsets 撑开 ItemView 上、下、左、右四个方向的空间

  • onDraw 在 ItemView 内容之下绘制图形

  • onDrawOver 在 ItemView 内容之上绘制图形。

完整源码地址:

http://download.csdn.net/detail/briblue/9813393

github地址

https://github.com/frank909zhao/ItemDecorationDemo

更多

每天学习累了,看些搞笑的段子放松一下吧。关注最具娱乐精神的公众号,每天都有好心情。

RecyclerView之ItemDecoration讲解及高级特性实践

如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。

欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号:

RecyclerView之ItemDecoration讲解及高级特性实践