自定义LayoutManager带你撸个LinearLayoutManager

*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

之前就想写这篇文章,奈何没有彻底弄懂自定义LayoutManager机制,导致写到一半时候就感觉无从下手了,就搁浅停笔了。而现在终于可以完成这篇文章了。
ok,开门见山不废话,直接分析实现一个跟系统LinearLayoutManager一样的自定义LayoutManager。

RecyclerView由于解耦的比较彻底,所以可定制性也非常的强,我们设置LinearLayoutManager的时候所有的控件就会水平或者垂直排列,设置GridLayoutManager时就会呈现网格排列,设置StaggeredGridLayoutManager就会瀑布流排列。
虽然系统提供的这些控件足以应对大多数产品99%的需求,但是这么神奇的东东还是值得去研究一下的。
我们都知道自定义ViewGroup的时候需要去继承ViewGroup,那么同样我们自定义LayoutManager的时候也需要去继承LayoutManager这个类。

QQ20170806-220748@2x.png

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

QQ20170806-220946@2x.png

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

下面是部分截取源码:

20160706173959985.jpeg

具体可以参考这篇文章:
http://blog.csdn.net/overseasandroid/article/details/51840819

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

QQ20170806-222538@2x.png

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

QQ20170806-223222@2x.png

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

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

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

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

QQ20170806-224358@2x.png

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

QQ20170806-224705@2x.png

一开始我也就这么实现,但是当我好奇把itemCount加到几万,甚至几十万条数据时。。。你懂的,爆炸了~~~
所以说国内现象是很多人不喜欢思考,ctrl+c、v 实现了完事。。。。

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

QQ20170806-225130@2x.png
QQ20170806-225344@2x.png

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

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

QQ20170806-225702@2x.png

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

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

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

Untitled.gif

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

这里我重新封装一个fillViews()方法用于回收复用view
注意这里跟上面填充的方法是不同的(参数数量不同),看下我是怎么实现的

QQ20170806-231300@2x.png
这里需要小小注意下,可以看到,onCreateViewHolder( )和onBindViewHolder()都是执行在Recyclerview去addView()之前的.

这里判断当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://www.jianshu.com/p/534a6e356dce

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

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

QQ20170806-233015@2x.png
dongtao111111.gif

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

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

不惯性.gif

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

惯性.gif

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

22333.png

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 172,183评论 25 707
  • RecyclerView 源码分析 本文原创,转载请注明出处。欢迎关注我的 简书 ,关注我的专题 Android ...
    MeloDev阅读 10,111评论 6 49
  • 简介: 提供一个让有限的窗口变成一个大数据集的灵活视图。 术语表: Adapter:RecyclerView的子类...
    酷泡泡阅读 5,165评论 0 16
  • 一袭白衫执剑江湖 冷漠俗尘自独孤 打马江南烟雨绵绵 马蹄踏在青石古道上 哒哒声和丝雨和弦 惊醒了哪家姑娘 偷偷推开...
    壹介书生阅读 274评论 1 1
  • 继续多看书吧……至少比女人靠谱。
    鹰王守仁阅读 183评论 0 0