自定义LayoutManager带你撸个LinearLayoutManager

今日快讯

9月6日,教育部财务司副司长赵建军今天在教育部新闻发布会上表示,根据规范校园贷管理文件,任何网络贷款机构都不允许向在校大学生发放贷款。为了满足学生金融消费的需要,鼓励正规的商业银行开办针对大学生的小额信用贷款。

作者简介

本篇来自 boboyuwu 的投稿,分享了一个自定义的 LayoutManager,希望大家喜欢!

boboyuwu 的博客地址:

http://blog.csdn.net/boboyuwu

前言

之前就想写这篇文章,奈何没有彻底弄懂自定义LayoutManager机制,导致写到一半时候就感觉无从下手了,就搁浅停笔了。而现在终于可以完成这篇文章了。 

正文

ok,开门见山不废话,直接分析实现一个跟系统 LinearLayoutManager 一样的自定义 LayoutManager。

RecyclerView 由于解耦的比较彻底,所以可定制性也非常的强,我们设置 LinearLayoutManager 的时候所有的控件就会水平或者垂直排列,设置 GridLayoutManager 时就会呈现网格排列,设置 StaggeredGridLayoutManager 就会瀑布流排列。 

虽然系统提供的这些控件足以应对大多数产品99%的需求,但是这么神奇的东东还是值得去研究一下的。 

我们都知道自定义 ViewGroup 的时候需要去继承 ViewGroup,那么同样我们自定义 LayoutManager 的时候也需要去继承 LayoutManager 这个类。

随便起名一个CustomeLayoutManager类,继承这个类需要实现一个方法

这个方法是必须实现的,那么实现它有什么作用呢?简单的解释下,假设我们平时调用 LayoutInflate.inflate(resource , null, false) 第二个参数传的是null或者直接使用 View.inflate() 去填充 View 的时候那么填充的 View 是没有布局参数的,那么当我们的 Recyclerview 去 addView() 时就会进行判断,如果 childView 的布局参数为 null 就是调用这个方法去生成一个默认的布局参数。 

下面是部分截取源码:

具体可以参考这篇文章: 

http://blog.csdn.net/overseasandroid/article/details/51840819

自定义 ViewGroup 要重写并实现一个 onLayout() 方法,我们这里类似需要实现 onLayoutChildren() 这个方法

首先我们需要判断 itemCount 是否为空,state.isPreLayout() 是判断之前布局时动画有没有处理结束,这里我们假设所有的 itemView 的宽和高都是相等的,创建第一个 view 并测量得到这个 view 的宽和高,注意这里我们调用的是 getDecoratedMeasuredWidth 方法而不是 getMeasuredWidth

这个方法会得到 view 控件的宽、高和 ItemDecoration的top,bottom,left,right 进行叠加我们需要考虑添加 ItemDecoration 这种情况。

最后把这个 view 放入 scap 中,scap 是轻量级缓存集合,通常被 detach 但会在同一布局重新使用的视图会临时储存在这里,它不需要进行重新绑定数据。

ok,拿到第一个控件的宽高之后,就可以大干一场了,创建一个集合这里用 SparseArray 就行了,因为我们的 key 都是 index 下标使用 SparseArray 效率比较高, 
遍历数据集把每一个 itemview 的位置都计算出来然后放进集合里。

我这里把填充 view 单独抽取到一个方法里防止还要使用

这里就简单的填充下,注意网上大多数甚至基本我看到的所有教程、博客基本都是统一这么写的,

一开始我也就这么实现,但是当我好奇把 itemCount 加到几万,甚至几十万条数据时。。。你懂的,爆炸了~~~ 

所以说国内现象是很多人不喜欢思考,ctrl+c、v 实现了完事。。。。

我这里改进了下,开始就填充,屏幕可见数量,在 onBindViewHolder 中打印下 log 可以看到确实是合格的,而官方的 LayoutManager 也是默认填充这么多滴!

可以说我实现的这个达到官方要求啦~~啊哈小小的自恋一下。

ok,布局已经全部填充完毕,滑动一下,咦!没有任何反应,别着急,我们还需要实现一些方法才可以让它动起来,

当我们需要垂直滚动时就重写scrollVerticallyBy这个方法,它会把垂直滚动的偏移量传递给到dy里。

这里进行下逻辑判断 

  • dy>0:向上滚动 

  • dy<0:向下滚动 

这里处理一下边界越界情况,最后将实际位移距离应用给子视图,注意这里返回的偏移量不能算错了,因为返回值被用来决定什么时候取消 flings,如果返回错误的值会让你失去对 content fling 的控制,并且正确的返回值还可以正确拥有边缘发光效果。

好了我们实现这个方法后已经可以动了,看下我录制的 gif 图

最后我们还需要完成最后一件事,也是最重要的一件事,recyclerview 最神奇的地方就在于它的 item 控件是可以复用的,这样大大的节省了内存所占用空间并提高了加载控件的效率,当初使用它的时候就被这一神奇特性惊叹到,ok 现在我们自己去实现这个特性。

这里我重新封装一个 fillViews() 方法用于回收复用 view

注意这里跟上面填充的方法是不同的(参数数量不同),看下我是怎么实现的

这里判断当 childCount 不为0的时候遍历 recyclerview 的每一个 child 然后拿到当前每一个 child 的位置跟屏幕显示大小进行比较,这里屏幕的范围为0~屏幕的高,如果不在这个范围内就回收掉它等待复用。

还记得一开始我们那个存储了所有 view 位置的集合吗,现在有它用武之地了,遍历所有的 view 跟当前偏移的屏幕显示大小进行比较,在这个范围内的,我们获取到 view 后并进行位置的填充,注意!这里屏幕显示大小的范围不再是0~屏幕的高了而是我们滚动时记录的 位置偏移量~位置偏移量+屏幕的高。

可能这里你会有疑问,回收的view我们是怎么拿到的呢?就是这个方法 getViewForPosition(), recyclerview 有多级缓存,它会去 recyclerview 的所有缓存中去找,比如最先会在 scap 中找,如果找到会比较这个当前 position 跟 scap 缓存中 viewHolder 的 position 是否一致,如果一致直接返回这个 viewholder 并且不需要进行 rebinding 数据,如果找不到或者 position 不一致再去 caches 缓存中去找,如果所有的缓存中都找不到就会调用 mAdapter.onCreateViewHolder() 方法去创建一个全新的 viewholder 。

具体细节可以看我这篇关于 recyclerview 回收分析: 

http://blog.csdn.net/boboyuwu/article/details/77148302

到此为止,一个简单的自定义 LinearLayoutManager 就诞生了,并且特性和效果跟官方的基本一致,当然我们自然没有官方提供控件考虑的那么周到并且测试的也没有那么全面,毕竟官方的控件还是提供了非常多的功能,代码逻辑要更之复杂,但是基本的功能已经完全实现了,我们看下效果吧

在 onCreateViewHolder 方法里并创建一个累加值打印下 log 观看下 viewholder 创建情况

可以看到从第16条数据开始,完全复用的是前面回收的 view,之后一直都是在 binding 数据而没有重新创建 viewholder 了,这个gif效果图跟官方 LinearLayoutManager 是一摸一样的感兴趣的可以自己试验下,这里就不再截图了。

最后还有个中间小插曲,就是一开始在一定滑动速度下,这个 view 的回收复用都是正常的,但是手指以超速 fling 状态下惯性滑动复用就失效了,这里录了个效果图,可以对比看下 

这是不惯性滑动,一切都是正常的

惯性滑动后尼玛就疯了~~~有多少数据创建多少 view

纠结了半天去排查原因,最后发现原来是惯性滑动时第一次 dy 这个值传的就非常大,可能会直接超出一个 view 高度以上

而问题就出现在这里,假如第一个事件的值就特别大,recyclerview 的第一个 child 和第二个 child 全都偏移出了屏幕可见范围,在这里遍历child时第一个 child 由于不可见被移除并回收,那么之前的 position 位置1此时会变成0,而我们的i此时却是1,此时getChildAt(1)获取到的 view 就是回收之前的 position 2所在的 view,所以我们就漏回收了 position 1这个位置的 view 导致回收逻辑出现问题,解决的方法就是把i–注释去掉即可。

结语

好了,到这里已经系统的学习了下自定义layoutmanager过程,了解了这些过程我相信做出其他的效果也只是时间的问题~~

更多

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

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

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

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值