android RecyclerView (二) ItemDecoration 详解
RecyclerView 已经推出了一年多了,日常开发中也已经彻底从 ListView 迁移到了 RecyclerView,但前两天有人在一个安卓群里面问了个关于最顶上的 item view 加蒙层的问题,被人用 ItemDecoration 完美解决。此时我发现自己对 RecyclerView 的使用一直太过基本,更深入更强大的功能完全没有涉及,像 ItemDecoration, ItemAnimator, SmoothScroller, OnItemTouchListener, LayoutManager 之类,以及 RecyclerView 重用 view 的原理。网上也有很多对 RecyclerView 使用的讲解博客,要么讲的内容非常少,要么提到了高级功能,但是并没讲代码为什么这样写,每个方法和参数的含义是什么,像张鸿洋的博客,也讲了 ItemDecoration 的使用,但是看了仍然云里雾里,只能把他的代码拿来用,并不能根据自己的需求编写自己的 ItemDecoration。
在这个系列中,我将对上述各个部分进行深入研究,目标就是看了这一系列的文章之后,开发者可以清楚快捷的根据自己的需求,编写自己需要的各个高级模块。本系列第一篇就聚焦在:RecyclerView.ItemDecoration。本文涉及到的完整代码可以在Github 获取。
TL; DR
- getItemOffsets 中为 outRect 设置的4个方向的值,将被计算进所有 decoration 的尺寸中,而这个尺寸,被计入了 RecyclerView 每个 item view 的 padding 中
- 在 onDraw 为 divider 设置绘制范围,并绘制到 canvas 上,而这个绘制范围可以超出在 getItemOffsets 中设置的范围,但由于 decoration 是绘制在 child view 的底下,所以并不可见,但是会存在 overdraw
- decoration 的 onDraw,child view 的 onDraw,decoration 的 onDrawOver,这三者是依次发生的
- onDrawOver 是绘制在最上层的,所以它的绘制位置并不受限制
RecyclerView.ItemDecoration
这个类包含三个方法 1:
onDraw(Canvas c, RecyclerView parent, State state)
onDrawOver(Canvas c, RecyclerView parent, State state)
getItemOffsets(Rect outRect, View view, RecyclerView parent, State state)
getItemOffsets
官方样例的 DividerItemDecoration
里面是这样实现的:
if (mOrientation == VERTICAL_LIST) {
outRect.set(0, 0, 0, mDivider.getIntrinsicHeight());
} else {
outRect.set(0, 0, mDivider.getIntrinsicWidth(), 0);
}
这个outRect设置的四个值是什么意思呢?先来看看它是在哪里调用的,它在RecyclerView中唯一被调用的地方就是 getItemDecorInsetsForChild(View child)
函数。
Rect getItemDecorInsetsForChild(View child) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (!lp.mInsetsDirty) {
return lp.mDecorInsets;
}
final Rect insets = lp.mDecorInsets;
insets.set(0, 0, 0, 0);
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
lp.mInsetsDirty = false;
return insets;
}
可以看到,getItemOffsets
函数中设置的值被加到了 insets
变量中,并被该函数返回,那么 insets 又是啥呢?
insets 是啥?
根据Inset Drawable文档,它的使用场景是:当一个view需要的背景小于它的边界时。例如按钮图标较小,但是我们希望按钮有较大的点击热区,一种做法是使用ImageButton,设置background="@null"
,把图标资源设置给src
属性,这样ImageButton可以大于图标,而不会导致图标也跟着拉伸到ImageButton那么大。那么使用Inset drawable也能达到这样的目的。但是相比之下有什么优势呢?src
属性也能设置selector drawable,所以点击态也不是问题。也许唯一的优势就是更“优雅”吧 :)
回到正题,getItemDecorInsetsForChild 函数中会重置 insets 的值,并重新计算,计算方式就是把所有 ItemDecoration 的 getItemOffsets 中设置的值累加起来 2,而这个 insets 实际上是 RecyclerView 的 child 的 LayoutParams 中的一个属性,它会在 getTopDecorationHeight
, getBottomDecorationHeight
等函数中被返回,那么这个 insets 的意义就很明显了,它记录的是所有 ItemDecoration 所需要的 3尺寸的总和。
而在 RecyclerView 的 measureChild(View child, int widthUsed, int heightUsed)
函数中,调用了 getItemDecorInsetsForChild,并把它算在了 child view 的 padding 中。
public void measureChild(View child, int widthUsed, int heightUsed) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;
final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
canScrollHorizontally());
final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
canScrollVertically());
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
child.measure(widthSpec, heightSpec);
}
}
上面这段代码中调用 getChildMeasureSpec
函数的第三个参数就是 child view 的 padding,而这个参数就把 insets 的值算进去了。那么现在就可以确认了,getItemOffsets 中为 outRect 设置的4个方向的值,将被计算进所有 decoration 的尺寸中,而这个尺寸,被计入了 RecyclerView 每个 item view 的 padding 中。
PoC
这一步测试主要是对 getItemOffsets 函数传入的 outRect 参数各个值的设置,以证实上述分析的结论。
可以看到,当 left, top, right, bottom 全部设置为50时,RecyclerView 的每个 item view 各个方向的 padding 都增加了,对比各种情况,确实 getItemOffsets 中为 outRect 设置的值都将被计入 RecyclerView 每个 item view 的 padding 中。
onDraw
先来看看官方样例的 DividerItemDecoration
实现:
public void drawVertical(Canvas c, RecyclerView parent) {
final int left = parent.getPaddingLeft();
final int right = parent.getWidth() - parent.getPaddingRight();
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 +
Math.round(ViewCompat.getTranslationY(child));
final int bottom = top + mDivider.getIntrinsicHeight();
mDivider.setBounds(left, top, right, bottom);
mDivider.draw(c);
}
}
drawVertical 是为纵向的 RecyclerView 绘制 divider,遍历每个 child view 4 ,把 divider 绘制到 canvas 上,而 mDivider.setBounds
则设置了 divider 的绘制范围。其中,left 设置为 parent.getPaddingLeft(),也就是左边是 parent 也就是 RecyclerView 的左边界加上 paddingLeft 之后的位置,而 right 则设置为了 RecyclerView 的右边界减去 paddingRight 之后的位置,那这里左右边界就是 RecyclerView 的内容区域 5了。top 设置为了 child 的 bottom 加上 marginBottom 再加上 translationY,这其实就是 child view 的下边界 6,bottom 就是 divider 绘制的下边界了,它就是简单地 top 加上 divider 的高度。
PoC
这一步测试主要是对 onDraw 函数中对 divider 的绘制边界的设置。
可以看到,当我们把 left, right, top 7 设置得和官方样例一样,bottom 设置为 top + 25
,注意,这里 getItemOffsets 对 outSets 的设置只有 bottom = 50
,也就是 decoration 高度为50,我们可以看到,decoration 的上半部分就绘制为黑色了,下半部分没有绘制。而如果设置top = child.getBottom() + params.bottomMargin - 25
,bottom = top + 50
,就会发现 child view 的底部出现了 overdraw。所以这里我们可以得出结论:在 onDraw 为 divider 设置绘制范围,并绘制到 canvas 上,而这个绘制范围可以超出在 getItemOffsets 中设置的范围,但由于 decoration 是绘制在 child view 的底下,所以并不可见,但是会存在 overdraw。
onDrawOver
有一点需要注意:decoration 的 onDraw,child view 的 onDraw,decoration 的 onDrawOver,这三者是依次发生的。而由于 onDrawOver 是绘制在最上层的,所以它的绘制位置并不受限制(当然,decoration 的 onDraw 绘制范围也不受限制,只不过不可见),所以利用 onDrawOver 可以做很多事情,例如为 RecyclerView 整体顶部绘制一个蒙层,或者为特定的 item view 绘制蒙层。这里就不单独进行测试了,请见下一节的整体效果。
All in together
实现的效果:除了最后一个 item view,底部都有一个高度为25的黑色 divider,为整个 RecyclerView 的顶部绘制了一个渐变的蒙层。效果图如下:
小结
- getItemOffsets 中为 outRect 设置的4个方向的值,将被计算进所有 decoration 的尺寸中,而这个尺寸,被计入了 RecyclerView 每个 item view 的 padding 中
- 在 onDraw 为 divider 设置绘制范围,并绘制到 canvas 上,而这个绘制范围可以超出在 getItemOffsets 中设置的范围,但由于 decoration 是绘制在 child view 的底下,所以并不可见,但是会存在 overdraw
- decoration 的 onDraw,child view 的 onDraw,decoration 的 onDrawOver,这三者是依次发生的
- onDrawOver 是绘制在最上层的,所以它的绘制位置并不受限制
脚注
不算被 Deprecated 的方法 ↩
把 left, top, right, bottom 4个属性分别累加 ↩
也就是在 getItemOffsets 函数中为 outRect 参数设置的4个属性值 ↩
child view,并不是 adapter 的每一个 item,只有可见的 item 才会绘制,才是 RecyclerView 的 child view ↩
可以类比 CSS 的盒子模型,一个 view 包括 content, padding, margin 三个部分,content 和 padding 加起来就是 view 的尺寸,而 margin 不会增加 view 的尺寸,但是会影响和其他 view 的位置间距,但是安卓的 view 没有 margin 的合并 ↩
bottom 就是 content 的下边界加上 paddingBottom,而为了不“吃掉” child view 的底部边距,所以就加上 marginBottom,而 view 还能设置 translation 属性,用于 layout 完成之后的再次偏移,同理,为了不“吃掉”这个偏移,所以也要加上 translationY ↩
这里由于并没有对 child view 设置 translation,为了代码简短,就没有减去 translationY,实际上是需要的 ↩
借鉴:https://blog.piasy.com/2016/03/26/Insight-Android-RecyclerView-ItemDecoration/
android RecyclerView (二) ItemDecoration 详解的更多相关文章
- 《Android NFC 开发实战详解 》简介+源码+样章+勘误ING
<Android NFC 开发实战详解>简介+源码+样章+勘误ING SkySeraph Mar. 14th 2014 Email:skyseraph00@163.com 更多精彩请直接 ...
- ANDROID L——Material Design详解(UI控件)
转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! Android L: Google已经确认Android L就是Android Lolli ...
- 给 Android 开发者的 RxJava 详解
我从去年开始使用 RxJava ,到现在一年多了.今年加入了 Flipboard 后,看到 Flipboard 的 Android 项目也在使用 RxJava ,并且使用的场景越来越多 .而最近这几个 ...
- [转]ANDROID L——Material Design详解(动画篇)
转载请注明本文出自大苞米的博客(http://blog.csdn.net/a396901990),谢谢支持! 转自:http://blog.csdn.net/a396901990/article/de ...
- ViewPager 详解(二)---详解四大函数
前言:上篇中我们讲解了如何快速实现了一个滑动页面,但问题在于,PageAdapter必须要重写的四个函数,它们都各有什么意义,在上节的函数内部为什么要这么实现,下面我们就结合Android的API说明 ...
- Android屏幕适配问题详解
上篇-Android本地化资源目录详解 :http://www.cnblogs.com/steffen/p/3833048.html 单位: px(像素):屏幕上的点. in(英寸):长度单位. mm ...
- Android的init过程详解(一)
Android的init过程详解(一) Android的init过程(二):初始化语言(init.rc)解析 本文使用的软件版本 Android:4.2.2 Linux内核:3.1.10 本文及后续几 ...
- android ------- 开发者的 RxJava 详解
在正文开始之前的最后,放上 GitHub 链接和引入依赖的 gradle 代码: Github: https://github.com/ReactiveX/RxJava https://github. ...
- Android JNI作用及其详解
Android JNI作用及其详解 Java Native Interface (JNI)标准是Java平台的一部分,它允许Java代码和其他语言写的代码进行交互.JNI 是本地编程接口,它使得在 J ...
随机推荐
- C++ 静多态与动多态
多态是指通过单一的标识支持不同的特定行为的能力. C++中有两种多态,称为动多态(运行期多态)和静多态(编译期多态),而静多态主要通过模板来实现,宏也是实现静多态的一种途径. 动多态在C++中是通过虚 ...
- Hadoop2.5.2 安装部署
0x00 平台环境 OS: CentOS-6.5-x86_64 JDK: jdk-8u111-linux-x64 Hadoop: hadoop-2.5.2 0x01 操作系统基本设置 1.1 网络配置 ...
- linux 下vim文件乱码 cat文件正常处理方法
linux 下vim文件乱码 cat文件正常处理方法 服务器支持中文字符集,cat和其他查看文件命令现在正常,vim还是出现了中文乱码问题, 1.查看文件编码格式 vim 文件 :set fileen ...
- ceph存储池基本管理
一,设置默认存储池的pg或pgp的值(推荐100左右),在ceph.conf文件里增加: osd pool default pg num = osd pool default pgp num = 二, ...
- Base Class 慎用箭头函数
在项目中,child继承base的时候,需要重新修改base.fun的逻辑,但是有些情况下面并不是简单的覆盖,而是在base.fun的逻辑基础上进行加工处理. 刚开始接触es6的时候也许都遇到过,ch ...
- IEEEXtreme 10.0 - Goldbach's Second Conjecture
这是 meelo 原创的 IEEEXtreme极限编程大赛题解 Xtreme 10.0 - Goldbach's Second Conjecture 题目来源 第10届IEEE极限编程大赛 https ...
- [实战]MVC5+EF6+MySql企业网盘实战(19)——BJUI和ztree
写在前面 上周在博客园看到一篇通用权限系统的文章,看到他那个UI不错,这里就研究了一下,将网盘的UI修改为他的那个,感兴趣的可以参考:http://b-jui.com/ 系列文章 [EF]vs15+e ...
- js正则获取url参数,包含hash[#]和search[?]两种通用
function getQueryString(name) { var reg = new RegExp("(^|&)" + name + "=([^&] ...
- LoadRunner 使用虚拟IP测试流程
LoadRunner 使用虚拟IP测试流程 LoadRunner 使用IP欺骗的原因 . 当某个IP的访问过于频繁,或者访问量过大是,服务器会拒绝访问请求,这时候通过IP欺骗可以增加访问频率和访问量, ...
- centOS7.0配置防火墙
之前用的iptables来管理的防火墙,后来发现centOS7.0中已经用firewalld取代iptables了,于是与时俱进,停用了iptables. systemctl stop iptable ...