昨日,vivo与NBA在上海举办了战略发布会,宣布vivo将成为NBA在中国唯一的手机市场官方合作伙伴。未来多年里,双方将在赛事、产品、服务以及品牌营销等方面展开全方位战略合作。
根据内部人士透露,vivo将会推出一系列NBA主题的深度定制手机以及周边产品。同时,在未来多年中参与NBA国际系列赛中国站、NBA球迷答谢之夜等一系列品牌活动。此外,vivo还将推出“vivo校园篮球计划”、“vivo走进赛场”等一系列品牌定制的NBA主题活动。
本篇来自 张旭童 的投稿,分享了如何使用 ItemDecoration 实现 悬停头部分组列表功能,这种实现方式还是很简便的,希望能帮助有需要的朋友。
张旭童 的博客地址:
http://blog.csdn.net/zxt0601
效果如下,两个ItemDecoration,一个实现悬停头部分组列表功能,一个实现分割线:
网上关于实现带悬停分组头部的列表的方法有很多,像我看过有主席的自定义 ExpandListView 实现的,也看过有人用一个额外的父布局里面套 RecyclerView/ListView + 一个头部View(位置固定在父布局上方)实现的。
对于以上解决方案,有以下几点个人觉得不好的地方:
1. 现在 RecyclerView 是主流。
2. 在 RecyclerView 外套一个父布局总归是增加布局层级,容易overdraw,显得不够优雅。
3. item布局 实现带这种分类头部的方法有两种,一种是把分类头部当做一种itemViewtype(麻烦),另一种是每个 Item布局 都包含了分类头部的布局,代码里根据postion 等信息动态 Visible,Gone头部(布局冗余,item效率降低)。
况且Google为我们提供了 ItemDecoration,它本身就是用来修饰 RecyclerView 里的Item 的,它的 getItemOffsets()、 onDraw() 方法用于为Item分类头部留出空间和绘制(解决缺点3),它的 onDrawOver() 方法用于绘制悬停的头部View(解决缺点2)。
而且更重要的是,ItemDecoration 出来这么久了,你还不用它?
本文就利用 ItemDecoration 打造 分组列表,并配有悬停头部功能。
亮点预览:添加多个ItemDecoration、它们的执行顺序、ItemDecoration方法执行顺序、ItemDecoration和RecyclerView的绘制顺序。
用法:为 RecyclerViewPool 添加 一个或多个 ItemDecoration:
为 RecyclerView 添加 ItemDecoration 只要这么一句 addItemDecoration()。
它有两个同名重载方法:
addItemDecoration(ItemDecoration decor) 常用,(按照add顺序,依次渲染ItemDecoration)
addItemDecoration(ItemDecoration decor, int index) add一个ItemDecoration,并为它指定顺序
上来就高能,别的讲解 RecyclerView 的文章一般都是对 ItemDecoration 一笔带过,用的Demo 一般也都是官方的 DividerItemDecoration类,更别提还添加多个 ItemDecoration了。其实我也是写Demo的时候才发现这个方法,点进去查看了一下源码:
老套路:我们最常用的单参数方法 内部调用了双参数方法,并把index 传入-1。
我们 add 的 ItemDecoration 都存储在 RecyclerView 类的 mItemDecorations 变量里, 这个变量就是一个ArrayList,定义如下:
private final ArrayList mItemDecorations = new ArrayList();
常用(全部)方法,按照在 RecyclerView 中它们被调用的顺序排列:
这个三个方法也是继承一个 ItemDecoration 必须实现的三个方法。(其实 ItemDecoration 里除了@Deprecated 的方法 也就它们三了)
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state)
我们需要利用 parent 和 state 变量,来获取需要的辅助信息,例如 postion, 最终调用 outRect.set(int left, int top, int right, int bottom)方法,设置四个方向上需要为 ItemView 设置 padding 的值。
下图我觉得很经典:摘自(http://blog.piasy.com/2016/03/26/Insight-Android-RecyclerView-ItemDecoration/?utm_source=tuicool&utm_medium=referral):
本文的 实体bean 如下编写:
getItemOffsets 方法 如下:
通过 parent 获取 postion 信息,通过 postion 拿到数据里的每个bean里的分类,因为数据集已经有序,如果与前一个分类不一样,说明是一个新的分类,则需要绘制头部outRect.set(0, mTitleHeight, 0, 0),否则不需要 outRect.set(0, 0, 0, 0)。
public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state)
我们需要利用 parent 和 state 变量,来获取需要的辅助信息,例如绘制的上下左右,childCount, childView 等。最终利用 Canvas c 调用 Canvas 的方法来绘制出我们想要的UI。会自定义View就会写本方法。
onDraw 绘制出的内容是在 ItemView 下层,虽然它可以绘制超出 getItemOffsets() 里的Rect区域,但是超出区域最终不会显示,但被 ItemView 覆盖的区域会产生 OverDraw。
本文如下编写:通过parent获取绘制UI的 left 和 right 以及 childCount, 遍历 childView,根据 childView 的 postion,和方法一中的判断方法一样,来决定是否绘制分类Title区域。
分类绘制 title 的方法就是 自定义View 的套路,根据确定的上下左右范围先 drawRect 绘制一个背景,然后 drawText 绘制文字。
写完方法1、2,就已经完成了分类列表title的绘制,方法3实现顶部悬停title效果。
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state)
和 onDraw() 方法类似, 我们需要利用 parent 和 state 变量,来获取需要的辅助信息,例如绘制的上下左右,position, childView等。最终利用 Canvas c 调用 Canvas 的方法来绘制出我们想要的UI。同样是会自定义View就会写本方法。
onDrawOver 绘制出的内容是在 RecyclerView 的最上层,会遮挡住 ItemView,So天生自带悬停效果,用来绘制悬停View再好不过。
本文如下编写:首先通过 parent 获取 LayoutManager(由于悬停分组列表的特殊性,写死了是 LinearLayoutManger),然后获取当前第一个可见 itemView 以及 postion,以及它所属的分类 title(tag),然后绘制悬停View的背景和文字(tag),可参考方法2里的书写,大同小异。
至此,我们的 带悬停头部的分组列表 的 ItemDecoration 就编写完毕了。
一.多个ItemDecoration,以及它们的绘制顺序。
就像上面用法提到的,可以为一个 RecyclerView 添加 多个ItemDecoration,那么 多个ItemDecoration 的绘制顺序是什么呢?
多个ItemDecoration 最终是存储在 RecyclerView 里的 mItemDecorations(ArrayList) 变量中,那我们就去 RecyclerView 的源码里搜一搜,看看哪些地方用到了 mItemDecorations。
发现在 draw() 和 onDraw() 方法里:按照在 mItemDecorations 里的 postion顺序,依次调用了 每个 ItemDecoration 的 onDrawOver 和 onDraw 方法。所以 后添加的ItemDecoration,如果和 前面的ItemDecoration 的绘制区域有重合的地方,会遮盖住前面的ItemDecoration(OverDraw)。
二. ItemDecoration和RecyclerView的Item的绘制顺序
在介绍ItemDecoration的三个方法时,我们提到过结论:
理由: 由上面代码可见, RecyclerView 的 draw() 方法中,在 super.draw(c) 方法调用完后,才调用 mItemDecorations.get(i).onDrawOver(c, this, mState);
而 super.draw(c) 方法就是直接调用 View 的 public void draw(Canvas canvas) 方法(如下图所示) 其中又先调用了 View(RecyclerView)的 onDraw() 方法;
在 RecyclerView 的 onDraw() 方法中,会调用 mItemDecorations.get(i).onDraw(c, this, mState);
所以onDraw最先调用,绘制在最底层
后调用了 View(ViewGroup)的 dispatchDraw(canvas) 方法,会执行 drawChild(Canvas canvas, View child, long drawingTime) 方法,绘制每个 itemView。
所以ItemView绘制在中间层
最后 super.draw(c) 走完,调用 mItemDecorations.get(i).onDrawOver(c, this, mState);
所以 再调用 ItemDecoration 的 onDrawOver,绘制在最上层。(从方法名字也可以看出哈)
View 的 draw() 方法如下:
RecyclerView相关的各个类,个个是宝,每一次探索都觉得如获至宝, 感觉利用ItemDecoration可以干很多事,可惜ItemDecoration貌似不能接受到用户的点击事件~要不我右侧导航栏都想用ItemDecoration实现了。
关于可以 add 多个ItemDecoration 这一点,想了一下,觉得很精妙,这是一种很好的设计思想,多个ItemDecoration 各司其职,如本文,采用官方 ItemDecoration 作分割线,自己又写一个 ItemDecoration 作 分类title 和 分类title 相关的 悬停title。用时根据需要,选择任意数量的“装饰品” ItemDecoration,来丰富你的 RecyclerView。可能我的low常规思想还是一个XXX类,使用时如果扩充功能,需要extends and code~但这样不同的功能就太耦合了,不利于复用。毕竟 “组合大于继承”。
点击最后 阅读原文 查看源码。
如果你有好的技术文章想和大家分享,欢迎向我的公众号投稿,投稿具体细节请在公众号主页点击“投稿”菜单查看。
欢迎长按下图 -> 识别图中二维码或者扫一扫关注我的公众号: