RecyclerView源码分析1-各模块介绍

参考

https://blog.csdn.net/fyfcauc/article/details/54140777

https://blog.csdn.net/fyfcauc/article/details/54379260

RecyclerView各模块介绍

分析源码之前先把一些模块或类了解一下,防止之后在分析时犯迷糊。

RecyclerView可以看作是AbsListView的进化版,在结构设计和模块划分上有了极大的进步,核心是将各项功能独立为模块,然后组合在一起构成RecyclerView。

  1. AdapterHelper: Update行为记录/整合/分发系统(时序魔法师,扮演了RecylerView和LayoutManager之间的Position调停者),让RecyclerView可以支持真正的局部更新,不必像ListView一样牵一发动全身,Update行为整合/分发功能是Prelayout机制实现的基础,进而推动了Predictive动画模式的实现。Update行为这次也被模型对象化了,带来的好处就是对Update的处理粒度从整体细化到了某个item。

负责处理 Adapter 里的数据集发生变化时的预处理。

  1. LayoutManager: 布局系统分化了View管理和布局职责,RecyclerView不再负责具体的布局细节,只需要作为功能性平台为LayoutMananger提供各项服务。

LayoutManager使用RecyclerView提供服务,经过测量计算等内部细节逻辑,将自己的布局方案应用到RecyclerView来实现最终的布局效果。

更灵活的是,RecyclerView甚至可以动态切换LayoutManager来实现整体布局的动态切换。

相比以前的ListView/GridView继承AbsListView来实现不同布局效果,是基于继承实现扩展的思路,而LayoutManager机制则是基于组合来实现扩展, 更符合组合优于继承的设计思想。

  1. ItemDecoration: View展示装饰器,提升View在展示层面的扩展性,可定制化程度比ListView的divider高的多,在View测量和View绘制给了使用者完全的自由。
  1. 动画系统提供了独立的动画管理和计算框架,借助于底层的ViewinfoStore模块和PreLayout机制,使用者不需要像实现常规动画那样关注整个动画的边边角角, 只需要在动画机制提供的几个回调点配置动画即可,动画的起点终点信息,View可以执行何种类型的动画,甚至View执行完动画后的释放,全部由动画系统代劳。Simple和Predictive两种动画模式基本可以涵盖绝大多数的动画场景。
  1. Recycler系统相较AbsListView的RecycleBin更进一步,提供了多级缓存,第二级缓存(ViewCacheExtension)是可选的,由使用者来自行定制,在缓存控制方面提供了扩展性。第三级缓存(RecyclerViewPool)则实现RecyclerView之间的缓存分享。整体上来说,对缓存的使用更加自由,效率也更高。
  1. ViewHolder机制被正式集成,和View的关联不必再借助于Tag,直接作为LayoutParam的扩展属性进行保存,实现了和相关View的关联。ViewHolder除了被使用者进行扩展外,还保存了对应的Item的元信息(如位置,状态等),这些元信息配合AdapterHelper实现了局部更新功能。很多时候,RecyclerView**面对的是ViewHolder而不是View,在抽象层次上的到了提升**。
  1. RecyclerView在实现中将信息保存职责也进行了细化和独立, State被引入来保存布局中的暂态信息,进一步, State也是LayoutManager和RecyclerView之间的通信手段,LayoutManager通过State了解当前布局阶段的信息。除此之外,State还提供一个可以保存Object的SparseArray(以及对其的读写)来作为扩展性的体现
  1. ChildHelper是一个精巧的设计, 为LayoutManager**模拟了一个理想状态的ViewGroup环境,作为一个中间层存在与LayoutManager和RecyclerView之间。ChildHelper消抹了RecyclerView(现实世界)和Adapter/Data(理想世界)之间的差异**。
  1. ViewInfoStore 记录 pre-layout 和 post-layout 阶段的 ViewHolder 状态信息,方便分析差异做相应的动画。
  1. RecyclerView的核心是优雅高效的解决一个差异: 数据改变后,界面呈现的旧数据和幕后新数据的差异

RecyclerView

RecyclerView的一些概念:

  1. 稳定态: 数据和展示一致。
  2. 不稳定态: 数据变化,展示还没有刷新完,数据和展示不一致的阶段。
  3. Item: 列表项,属于一个列表,一个抽象层概念,View只是Item的展示载体, Item既有对应数据, 又可以有对应的View。 在RecyclerView中,ViewHolder就是Item的落地实现,RecyclerView在大多数概念级操作中,只关注ViewHolder,不关注ViewHolder对应的View,因为概念层的操作不应该涉及到展现层
  4. Index: View层概念,ChildView在ViewGroup中的索引。
  5. Position: Item在列表(算是数据层吧)中的位置.
  6. AdapterPosition: 同Position,RecyclerView这样称呼是因为Adapter算是Data在RecyclerView的代言人。
  1. LayoutPosition: 一个Item在上一次布局结束时在Data中的位置。
  • LayoutPosition和AdapterPosition是矛盾的一体,大多时候(稳定态)item的这两个属性是相同的,但在不稳定状态下, 因为数据层发生了变化,视图还没有来得及刷新,导致Item在数据层的位置(AdapterPosition)已经被更新(因为数据确实已经改变了)了,但是LayoutPosition这个代表着最近一次布局结束后Item的Position需要等待刷新完毕才能更新,两者在这个时间段,是不一致的。 在视图根据新的数据刷新后,两者恢复一致,进入稳定态。这两个Position,在一次刷新过程中,都是需要的。
  • 对Adapter来说(Data角度),AdapterPosition代表现在,LayoutPosition代表过去。
  • 对RecyclerView来说(可以理解为View角度),LayoutPosition代表现在,AdapterPosition代表将来。

ViewHolder

isInvalid()

boolean isInvalid() {
    return (mFlags & FLAG_INVALID) != 0;
}

表示数据和position和type都无效,需要被重新bind


needsUpdate()

boolean needsUpdate() {
   return (mFlags & FLAG_UPDATE) != 0;
}

数据发生了变化,position有效,需要被重新bind


isBound()

boolean isBound() {
   return (mFlags & FLAG_BOUND) != 0;
}

This ViewHolder has been bound to a position; mPosition, mItemId and mItemViewType are all valid.


isRemoved()

boolean isRemoved() {
   return (mFlags & FLAG_REMOVED) != 0;
}

当item被删除时触发layout,做动画时会被标记。


isTmpDetached()

boolean isTmpDetached() {
   return (mFlags & FLAG_TMP_DETACHED) != 0;
}

当布局时会把当前屏幕上的view都给detach,然后加入到scrapViews,此时Viewholder都会被标记。


wasReturnedFromScrap()

boolean wasReturnedFromScrap() {
   return (mFlags & FLAG_RETURNED_FROM_SCRAP) != 0;
}

从scrapViews中获取的Viewholder会被标记。

 

 

LayoutManager

它是RecyclerView的布局管理者,RecyclerView在onLayout时,会利用它来layoutChildren,它决定了RecyclerView中的子View的摆放规则。但不止如此, 它做的工作还有:

  1. 测量子View
  2. 对子View进行布局
  3. 对子View进行回收
  4. 子View动画的调度
  5. 负责RecyclerView滚动的实现
  6. ...

 

ChildHelper

https://blog.csdn.net/fyfcauc/article/details/54175072

ChildHelper的引入

RecyclerView在处理消失动画时采用了 和 Animation/LayoutTransition效果相似,但是本质有区别的策略:

某个ChildView需要渐变消失动画效果,那么该ChildView在动画结束前不会从ViewGroup中被remove掉,在动画运行完以后才会被remove掉。

 

上述策略显然会造成不一致:Data中,ChildView对应的Item已经被remove了(数据remove不会因为有动画就延迟),但是对应的ChildView因为动画的原因还留在RecyvlerView中,RecyclerView的ChildView和Data在这个时间段是不一致的。这种情况下的ChildView可以被理解为是暂态的(在RecyclerView中,被称作为AnimatingView)。

 

上面的不一致很显然会对RecyclerView的正常运作产生影响,比如RecyclerView的LayoutManager在进行布局时是不会也不能考虑上面的AnimatingView的(概括的来讲)。 但是某些场景下,RecyclerView又需要能够感知到这些AnimatingView(比如View复用)。这就造成了两种观察ChildView的视角:

  • 从ViewGroup角度看,所有ChildView都是可见的,包括AnimatingView
  • 从Data角度看,只有能够映射到Data的ChildView才是可见的(笼统的来说)

 

为了区分两种视角 以及 支持两种视角下的ChildView操作,需要进行额外的逻辑处理,RecyclerView将这部分逻辑独立为一个单独的模块:ChildHelper

 

解析

RecyclerView尽管本身是一个ViewGroup,但是将ChildView管理职责全权委托给了ChildHelper,所有关于ChildView的操作都要通过ChildHelper来间接进行,ChildHelper成为了一个ChildView操作的中间层,getChildCount/getChildAt等函数经由ChildHelper的拦截处理,再下发给RecyclerView的对应函数,其参数或者返回结果会根据实际的ChildView信息进行改写。

 

考虑一下ChildHelper的模型:

  • 首先,我们需要能够支持两种视角, 那么普通ChildView和AnimatingView这类特殊的View要能够被区分,为了区分就需要维护一个映射表,ChildView的 position 作为key,value则是否是特殊的View。

    在ChildHelper的实现中,将普通ChildView看作是可见的,特殊的View看作是不可见的,只需要维护一个true/false即可。进一步的,为了节省空间和提升效率,ChildHelper采用了BitSet来实现这个映射,因为true/false信息1bit就足以储存了, 这个BitSet在ChildHelper中对应的类是Bucket类(Bucket可以根据操作动态的扩展自己的空间,其本身实现也值得一看)。BitSet的很多操作都是位运算,因此效率高。Bucket用1表示不可见View。

 

  • 其次,要支持两种视角下的ChildView操作, 比如getChildCount这个操作, 即应该有 得到所有ChildView的版本,也应该有 只得到所有可见ChildView的版本。

    ChildHelper就分别实现了两类办法,比如getChildCount返回可见ChildView的数量,getUnfilteredChildCount则返回了所有ChildView的数量。

     

  • 同时,为了查询某个View是可见还是不可见的,ChildHelper 还要维护一个不可见View的列表 mHiddenViews ( Bucket不方便提供这个信息)

 

这样,ChildHelper储存了足够的信息来区分ChildView以及支持不同视角的操作,不过,因为ChildHelper本身并不是一个ViewGroup(只是一个中间处理器),真正涉及到具体平台的ChildView操作(这里就是Android的ViewGroup的相关ChildView操作)还需要落地到RecyclerView, ChildHelper通过提供一个Callback给RecyclerView这种方式实现了真正操作实现的落地。 因此在RecyclerView初始化ChildHelper时可以看到需要提供一个Callback的具体实现来给ChildHelper。

 

ChildHelper维护了两套ChildView序列,一套序列只包含了可见的ChildView,另一套则包含了所有ChildView,序列在这里很重要,因为很多对ChildView的操作是以其在序列中的位置作为参数进行的。

 

举个例子

RecyclerView中有 A,B,C,D,E,F 6个ChildView,其中B,C,E是不可见的,

存在两个ChildView序列:

  1. 所有ChildView: A,B,C,D,E,F

    [2] 可见ChildView: A, D, F.

    对[1],getChildCount是6;对[2],getChildCount是3.

    对[1],getChildAt(1)得到的是B;对[2],getChildAt(1)得到的是D

    addView函数只针对可见ChildView, 如果指定了要add的index(在序列中的位置), 那么会根据真实ChildView的情况(包含不可见的view),对index进行偏移(getOffset函数非常关键,会基于Bucket算出合适的偏移结果),算出一个在真实ChildView序列中的index,将这个新的index作为View在ViewGroup中的index进行添加。在将View按照新的index进行添加后,Bucket中的映射关系也会进行相应的insert。

    比如: 在[2]的index 2位置插入G(addView(G, 2)), 那么两套序列会变为:

    [1] A,B,C,D,G,E,F

    [2] A,D,G,F

    addView传输的index是2, 经过ChildHelper转化,最后偏移为4插入到了RecyclerView中。

    综上可见,ChildHelper作为一个ChildView中间层对外提供两个ViewGroup环境,一个是真实的包含所有ChildView,另一个只包含了在Data层面可见的ChildView。外界不需要关心内部如何保存View信息以及计算index偏移,只需调用ViewGroup类方法即可。

    Bucket简介

    https://blog.csdn.net/u012227177/article/details/73381598

    RecyclerView中的ChildHelper.Bucket是一个工具类,实现了类似List<Boolean>的数据结构,从而达到减少内存占用的目的。

    public static class Bucket {
        final static int BITS_PER_WORD = Long.SIZE;
        final static long LAST_BIT = 1L << (Long.SIZE - 1);
        long mData = 0;//当前数据
        Bucket next;//指向下一个数据
    
        ...方法列表
    }

    Bucket是一个链表结构,

    mData是long类型,用于存储当前64位的信息,每一个bit位可以是0或者1,代表false或true。

    next指向下一个64位数据,因为一个long最多只能存64位,所以超出64位的用next表示。

    set方法

    public void set(int index)

    设置对应index位的bit为1(index从0开始)

    get方法

    public boolean get(int index)

    判断index对应的bit是否为1,如果为1,返回true,否则返回false。

    clear方法

    public void clear(int index)

    设置index对应的bit为0

    reset方法

    public void reset()

    重置,所有数据置为0

    insert方法

    public void insert(int index, boolean value)

    在index位置插入指定bit:value为true插入1,value为false插入0。

    低于index的数据保持不变,大于等于index的数据左移了一位。

    remove方法

    public boolean remove(int index)

    移除index对应位置的bit。

    低于index的所有数据保持不变,高于index的数据右移一位。

    countOnesBefore方法

    public int countOnesBefore(int index)

    计算比index小的所有位数上bit为1的总个数。

    例如 0001 1010,如果index为5,则结果为3;如果index为4,则结果是2

    源码:

    public int countOnesBefore(int index) {
        if (next == null) {
            if (index >= BITS_PER_WORD) {
                return Long.bitCount(mData);
            }
            return Long.bitCount(mData & ((1L << index) - 1));
        }
        if (index < BITS_PER_WORD) {
            return Long.bitCount(mData & ((1L << index) - 1));
        } else {
            return next.countOnesBefore(index - BITS_PER_WORD) + Long.bitCount(mData);
        }
    }

    State

    https://blog.csdn.net/fyfcauc/article/details/54343635

    State是最简单的RecyclerView子模块。

    RecyclerView在工作过程中,需要维护一系列状态信息(比如当前处于Layout的哪个阶段,是否处于preLayout阶段等)。并且因为RecyclerView将功能进行了子模块化,还需要传递某些信息到特定子模块来完成功能/通信,RecyclerView把这部分职责集中到了State模块中。State内部聚合了所需的各项状态信息,扮演了状态上下文角色。RecyclerView维护一个State实例,根据情况来存取State的某个状态信息。对于需要某些上下文信息的子模块,则直接将State实例传递过去(比如LayoutManager的onLayoutChildren函数就接受一个State参数作为当前的环境上下文),这样做,在函数层面会显得简洁,并且函数接口的扩展性比较强,后面如果需要新的状态信息,打包在State中传输即可,不需要再开一个新的函数参数,子模块会根据需要存取State的状态信息,在某种层面实现了子模块和RecyclerView以及子模块之间的通信/控制。

    State中维护以下状态信息:

  • mTargetPosition: RecyclerView当前滑动的终点Position, 如果当前没有在滑动,那么值为NO_POSITION。
  • mLayoutStep: 当前处于哪个Layout阶段,前面说过,RecylcerView的Layout分为3个阶段:
    • STEP_START: 初始状态,第一阶段还没有进行, 也作为这一轮Layout结束的标志状态。
    • STEP_LAYOUT: 第一阶段完毕,等待第二阶段开始(真正的布局: LAYOUT)。
    • STEP_ANIMATIONS: 第二阶段完毕,等待第三阶段开始(开始处理动画: ANIMATIONS)
  • mItemCount: 当前Data的item数量(有可能和界面上呈现的不一致,因为界面正在刷新同步中)
  • mPreviousLayoutItemCount: 上一次Layout结束时Data的item数量(Layout结束,界面和数据就同步了)
  • mDeletedInvisibleItemCountSincePreviousLayout: 和PreLayout有关,代表在PreLayout阶段就被remove的item的数量。
  • mStructureChanged: 本次发生的变化是否导致了结构上的变化(比如增加/删除都算结构上的改变,但是如果只有更新,就不算)
  • mInPreLayout: 当前是否在PreLayout阶段。
  • mRunSimpleAnimations/mRunPredictiveAnimations: 标识这次Layout支持的动画模式。
  • mTrackOldChangeHolders: 主要为了支持Animation, 这里不介绍。
  • mIsMeasuring: 当前是否正在测量中。
  • mFocusedItemPosition/mFocusedItemId/mFocusedSubChildId: 焦点相关,这里不介绍。

除了上述状态信息外, 为了增强State的通信功能,State内部还维护了一个Object SparseArray来支持扩展通信,State的使用者之间约定在SparseArray的某个特定位置(约定好一个int key)存取对象来传递附加信息。

LinearLayoutManager.LayoutState

仿照RecyclerView的State方案, LayoutManager在布局过程中需要的信息也被集成到LayoutState对象中进行统一的管理和存取,其中记录了在布局过程中需要使用的状态信息,并提供了一些功能函数,LayoutState中的某些状态甚至是为某一次View布局而记录的,可能每布局一个View,都会被更新, 挑一些重要的说:

  1. mOffset: 填充过程中下一个View的layout开始位置
  2. mAvailable: 在当前layout方向上,应该填充的距离
  3. mCurrentPosition: 下一个要填充的位置(注意,这个位置不是ViewGroup中的位置,而是Data中的位置)。
  4. mItemDirection: Data遍历方向(ITEM_DIRECTION_HEAD/ITEM_DIRECTION_TAIL)
  5. mLayoutDirection: 当前layout方向: LAYOUT_START/LAYOUT_END
  6. mScrollingOffset: 当LayoutState在滑动状态下被构造时,会被用到,代表在不创建新的ChildView的前提下,最多可以滑动的距离。
  7. mExtra: 用于PreLayout, 先不介绍。
  8. mIsPreLayout: 本次Layout是否属于PreLayout, 先不介绍。
  9. mLastScrollDelta: 上一次滑动的距离。
  10. hasMore(): 基于当前的mCurrentPosition得出该Position对应的item是否存在于Adapter的Data中。
  11. next(): 获取下一个要填充位置(也就是mCurrentPosition)对应的View(关键的View提供者), 同时还会基于mItemDirection**更新mCurrentPosition. 其最终会调到Recycler的getViewForPosition根据Position和ViewType从Recycler中获取一个View(可能是缓存复用的,也可能是新建的,onCreateViewHolder/onBindViewHolder都会在这一步被调用)**。

ItemDecoration

https://blog.csdn.net/fyfcauc/article/details/54291847

RecyclerView的ItemDecoration机制相对比较简单,不过扩展性很强,在ChildView的测量和展示上为使用者提供了极大的发挥空间,像divider/项目高亮/项目边框等效果都可以轻松实现

ItemDecoration可以同时存在复数个,维护在一个列表中,影响是可以叠加的,ItemDecoration的作用顺序从列表头到列表尾。

  1. addItemDecoration可以添加一个ItemDecoration。
  2. removeItemDecoration可以删除一个ItemDecoration。
  3. invalidateItemDecorations表示当前的ItemDecoration发生了变化,需要更新。
  4. 因为RecyclerView继承自ViewGroup,默认其willNoDraw是true(意味着其onDraw函数不会被调用),但是ItemDecoration的onDraw需要在RecyclerView的onDraw回调点进行,因此添加/删除ItemDecoraton时会根据情况改变willNotDraw属性**。
  5. 因为ItemDecoration会影响ChildView的测量,进而影响布局,还影响绘制,因此ItemDecoration的添加/删除/Invalidate会触发requestLayout来重新测量布局绘制.
  6. 在Scroll/Layout的过程中禁止添加/删除ItemDecoration。

Recycler

https://www.jianshu.com/p/aeb9ccf6a5a4

先列一些概念

  1. View的detach和remove:
  • detach: 在ViewGroup中的实现很简单,只是将ChildView**从ParentView的ChildView数组中移除,ChildView的mParent设置为null, 可以理解为轻量级的临时remove, 因为View此时和View树还是藕断丝连, 这个函数被经常用来改变ChildView在ChildView数组中的次序。**View被detach一般是临时的,在后面会被重新attach。
  • remove: 真正的移除,不光被从ChildView数组中除名,其他和View树各项联系也会被彻底斩断(不考虑Animation/LayoutTransition这种特殊情况), 比如焦点被清除,从TouchTarget中被移除等。
  1. RecyclerView的Scrap View:

Scrap View指的是在RecyclerView中,处于根据数据刷新界面等行为, ChildView被detach(注意这个detach指的是1中介绍的detach行为,而不是RecyclerView一部分注释中的"detach",RecyclerView一部分注释中的"detach"其实指得是上面的remove),并且被存储到了Recycler中,这部分ChildView就是Scrap View。

首先我们要了解,任何一个ViewGroup都会经历两次onLayout的过程,对应的childView就会经历detach和attach的过程,而在这个过程中,ScrapViews就起了缓存的作用,这样就不需要重复创建childView和bind。
所以ScrapView主要用于对于屏幕内的ChildView的缓存,缓存中的ViewHolder不需要重新Bind,缓存时机是在onLayout的过程中,并且用完即清空。

 

  1. ViewHolder有一个Flag: FLAG_TMP_DETACHED代表的就是1中介绍的detach, 这也印证了2的推测,RecyclerView将remove视为"detach", detach视为"tmp_detach"

 

  1. Recycler**一般不会直接作用于View,其操作的对象一般是ViewHolder**。Recycler**分几个地方(代表不同层级)存放可复用的ViewHolder**:

[一级缓存]: Scrap View: mAttachedScrap和mChangedScrap

[一级缓存]: Removed View: mCachedViews

[二级缓存]: ViewCacheExtension(可选可配置): 供使用者自行扩展,让使用者可以控制缓存

[三级缓存]: RecycledViewPool(可配置): RecyclerView之间共享ViewHolder的缓存池

 

 

对于LayoutManager来说,它是ViewHolder的提供者。对于RecyclerView来说,它是ViewHolder的管理者,是RecyclerView最核心的实现。下面这张图大致描述了它的组成:

 

scrap list

final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
ArrayList<ViewHolder> mChangedScrap = null;

相信你在许多RecyclerView的crash log中都看到过这个单词。它是指View在RecyclerView布局期间进入分离状态的子视图。即它已经被deatach(标记为FLAG_TMP_DETACHED状态)了。这种View是可以被立即复用的。它在复用时,如果数据没有更新,是不需要调用onBindViewHolder方法的。如果数据更新了,那么需要重新调用onBindViewHolder。

mChangedScrap : 用来保存RecyclerView做动画时,被detach的ViewHolder。

mAttachedScrap : 用来保存RecyclerView做数据刷新(notify),被detach的ViewHolder

mAttachedScrap和mChangedScrap中的View复用主要作用在adapter.notifyXXX时。这时候就会产生很多scrap状态的view。 也可以把它理解为一个ViewHolder的缓存。不过在从这里获取ViewHolder时完全是根据ViewHolder的position而不是item type。如果在notifyXX时data已经被移除掉你,那么其中对应的ViewHolder也会被移除掉。

mCacheViews

可以把它理解为RecyclerView的一级缓存。它的默认大小是2, 从中可以根据item type或者position来获取ViewHolder。可以通过RecyclerView.setItemViewCacheSize()来改变它的大小。

mCacheViews集合为什么要这样缓存?

看一下下面这张图 :

我是这样认为的,如上图,往上滑动一段距离,被滑动出去的ViewHolder会被缓存在mCacheViews集合,并且位置是被记录的。如果用户此时再下滑的话,可以参考文章开头的从Recycler中获取ViewHolder的逻辑:

  1. 先按照位置从mCacheViews集合中获取
  2. 按照viewType从mCacheViews集合中获取

上面对于mCacheViews集合两步操作,其实第一步就已经命中了缓存的ViewHolder。并且这时候都不需要调用Adapter.bindViewHolder()方法的。即是十分高效的。

所以在普通的滚动复用的情况下,ViewHolder的复用主要来自于mCacheViews集合, 旧的ViewHolder会被放到mCacheViews集合, mCacheViews集合挤出来的更老的ViewHolder放到了RecyclerViewPool中

RecycledViewPool

mCacheViews集合中装满时,会放到这里。

它是一个可以被复用的ViewHolder缓存池。即可以给多个RecycledView来设置统一个RecycledViewPool。这个对于多tab feed流应用可能会有很显著的效果。

它内部利用一个ScrapData来保存ViewHolder集合:

class ScrapData {
    final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
    int mMaxScrap = DEFAULT_MAX_SCRAP;   //最多缓存5个
    long mCreateRunningAverageNs = 0;
    long mBindRunningAverageNs = 0;
}
SparseArray<ScrapData> mScrap = new SparseArray<>();  //RecycledViewPool 用来保存ViewHolder的容器

一个ScrapData对应一种type的ViewHolder集合。看一下它的获取ViewHolder和保存ViewHolder的方法:

//
public void putRecycledView(ViewHolder scrap) {
    final int viewType = scrap.getItemViewType();
    final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
    if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size())  return; //到最大极限就不能放了
    scrap.resetInternal();  //放到里面,这个view就相当于和原来的信息完全隔离了,只记得他的type,清除其相关状态
scrapHeap.add(scrap);
}

//
private ScrapData getScrapDataForType(int viewType) {
    ScrapData scrapData = mScrap.get(viewType);
    if (scrapData == null) {
        scrapData = new ScrapData();
        mScrap.put(viewType, scrapData);
    }
return scrapData;
}

关于AdapterPosition和LayoutPosition

AdapterPosition: 同Position,RecyclerView这样称呼是因为Adapter算是Data在RecyclerView的代言人。

LayoutPosition: 一个Item在上一次布局结束时在Data中的位置。

  • LayoutPosition和AdapterPosition是矛盾的一体,大多时候(稳定态)item的这两个属性是相同的,但在不稳定状态下, 因为数据层发生了变化,视图还没有来得及刷新,导致Item在数据层的位置(AdapterPosition)已经被更新(因为数据确实已经改变了)了,但是LayoutPosition这个代表着最近一次布局结束后Item的Position需要等待刷新完毕才能更新,两者在这个时间段,是不一致的。 在视图根据新的数据刷新后,两者恢复一致,进入稳定态。这两个Position,在一次刷新过程中,都是需要的。
  • 对Adapter来说(Data角度),AdapterPosition代表现在,LayoutPosition代表过去。
  • 对RecyclerView来说(可以理解为View角度),LayoutPosition代表现在,AdapterPosition代表将来。

getAdapterPosition源码

ViewHolder.getAdapterPosition

public final int getAdapterPosition() {
    if (mOwnerRecyclerView == null) {
        return NO_POSITION;
    }
    return mOwnerRecyclerView.getAdapterPositionFor(this);
}

RecyclerView.getAdapterPositionFor

int getAdapterPositionFor(ViewHolder viewHolder) {
    if (viewHolder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID
            | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)
            || !viewHolder.isBound()) {
        return RecyclerView.NO_POSITION;
    }
    return mAdapterHelper.applyPendingUpdatesToPosition(viewHolder.mPosition);
}

AdapterHelper.applyPendingUpdatesToPosition

public int applyPendingUpdatesToPosition(int position) {
    final int size = mPendingUpdates.size();
    for (int i = 0; i < size; i++) {
        UpdateOp op = mPendingUpdates.get(i);
        switch (op.cmd) {
            case UpdateOp.ADD:
                if (op.positionStart <= position) {
                    position += op.itemCount;
                }
                break;
            case UpdateOp.REMOVE:
                if (op.positionStart <= position) {
                    final int end = op.positionStart + op.itemCount;
                    if (end > position) {
                        return RecyclerView.NO_POSITION;
                    }
                    position -= op.itemCount;
                }
                break;
            case UpdateOp.MOVE:
                if (op.positionStart == position) {
                    position = op.itemCount; //position end
                } else {
                    if (op.positionStart < position) {
                        position -= 1;
                    }
                    if (op.itemCount <= position) {
                        position += 1;
                    }
                }
                break;
        }
    }
    return position;
}

假如有如下操作:

adapter.notifyItemRangeInserted
ViewHolder.getAdapterPosition

adapter.notifyItemRangeInserted会把操作记录在AdapterHelper.mPendingUpdates中。

接着不等重新布局,立马获取adapterPosition,此时会调用到applyPendingUpdatesToPosition中:

          case UpdateOp.ADD:
                if (op.positionStart <= position) {
                    position += op.itemCount;
                }
                break;

notifyItemRangeInserted之前,position是ViewHolder的position。

然后会判断if (op.positionStart <= position),表示要在position之前插入新的item,然后就会position += op.itemCount,如此就可以得到最终的position。
这就是为什么adapterPosition要更迅速。

 

Predictive Animation预测动画

http://www.birbit.com/recyclerview-animations-part-1-how-animations-work/

https://www.jianshu.com/p/0b032dbb3951

https://www.youtube.com/watch?v=cwDqjmSmtMQ

https://frogermcs.github.io/recyclerview-animations-androiddevsummit-write-up/

 

在RecyclerView中有两种动画:

一种是SimpleAnimations,这种动画就是普通动画。

一种是Predictive Animation。

 

在这篇文章中,我想去深入剖析RecyclerView的内部实现原理,尤其是关于动画是如何实现的.
在Honeycomb中,Android 引入了布局动画LayoutTransition,来实现当ViewGroup布局变化时的过渡动画,这个框架会拿到ViewGroup布局变化前后的状态,然后在两种状态间创建动画进行改变.
但是,列表控件与普通ViewGroup有很大的区别,列表控件中的item与ViewGroup中的子view也有很大的区别,所以我们不能直接使用LayoutTransition。在普通ViewGroup中,如果View是被新加入到ViewGroup中的,它是被当做一个新的View对待的,并且可以使用fade in等动画。但是对于列表,例如,一个item的view变成可见的,可能是因为它前面的item从Adapter中被移除了。在这种情况下,如果使用fade in动画,就会让用户产生改item是被新插入的错觉,但是事实上这个item已经在列表中了,它应该是滚入屏幕的。RecyclerView知道这个item是否是新的,但是却不知道当这个item它原来的位置在哪.同样的,对于滚出屏幕的item(前提没有被adapter移除),RecyclerView同样不知道这个view要被放置在哪。

 

 

预取功能(Prefetch)

https://www.jianshu.com/p/1d2213f303fc

 

这个功能是rv在版本25之后自带的,也就是说只要你使用了25或者之后版本的rv,那么就自带该功能,并且默认就是处理开启的状态,通过LinearLayoutManager的setItemPrefetchEnabled()我们可以手动控制该功能的开启关闭,但是一般情况下没必要也不推荐关闭该功能,预取功能的原理比较好理解。

我们都知道android是通过每16ms刷新一次页面来保证ui的流畅程度,现在android系统中刷新ui会通过cpu产生数据,然后交给gpu渲染的形式来完成,从上图可以看出当cpu完成数据处理交给gpu后就一直处于空闲状态,需要等待下一帧才会进行数据处理,而这空闲时间就被白白浪费了,如何才能压榨cpu的性能,让它一直处于忙碌状态,这就是rv的预取功能(Prefetch)要做的事情,rv会预取接下来可能要显示的item,在下一帧到来之前提前处理完数据,然后将得到的itemholder缓存起来,等到真正要使用的时候直接从缓存取出来即可。

猜你喜欢

转载自www.cnblogs.com/muouren/p/11706509.html
今日推荐