• recyclerView源码分析


    RecyclerView  其功能的高度解耦化,规范 ViewHolder 的写法,以及对动画友好支持,都是它与传统控件 ListView 的区别。
    而无论 ListView 还是 RecyclerView,本质上都是在有限的屏幕之上,展示大量的内容。所以复用的逻辑,就成了它们最最重要的核心原理,

    RecyclerView 的几大模块 
    • ItemDecoration--为 RecyclerView 添加分割线
    • ItemAnimator--控制 RecyclerView 中 item 的动画
    • LayoutManager--负责 RecyclerView 中,控制 item 的布局方向 
    • RecyclerView.Adapter--为 RecyclerView 承载数据
    • Recycler--控制 RecyclerView 缓存的核心类
    刚刚我们提到 RecyclerView 的高度解耦,就是通过以上对象各司其职,来实现 RecyclerView 的基本功能。
    本文的重点就是缓存原理分析,不过在此之前,我们还是简单地分别介绍下以上中各个模块的源码。

    -->RecyclerView 缓存原理
    通常认为RecyclerView有四级缓存,RecyclerView的缓存是通过Recycler类来完成的,方法的入口:
    public View getViewForPosition(int position) {
    return getViewForPosition(position, false);
    }
    缓存的内容是ViewHolder,缓存的地方,是Recycler的几个list:
    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<ViewHolder>();
    private ArrayList<ViewHolder> mChangedScrap = null;
    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
    private final List<ViewHolder> mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
    private int mViewCacheMax = DEFAULT_CACHE_SIZE;
    private RecycledViewPool mRecyclerPool;//四级缓存
    private ViewCacheExtension mViewCacheExtension;
    private static final int DEFAULT_CACHE_SIZE = 2;
    第一级缓存:
    mAttachedScrap: 用于缓存显示在屏幕上的 item 的 ViewHolder
mChangedScrap: 还是屏幕内的ViewHolder,没看懂啥意思,可能是用于缓存局部改变时候的holder
    第二级缓存
    mCachedViews:划出屏幕外的item,这个list的默认大小是2,
    第三级缓存
    mViewCacheExtension:自定义缓存,RecyclerView默认是没有实现的,留给开发者自己实现
    第四级缓存
    mRecyclerPool:mCachedViews的数量达到上限之后,会把ViewHolder存入mRecyclerPool。mRecyclerPool用SparseArray来缓存进入这一级的ViewHolder:
    static class ScrapData {
    final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
    int mMaxScrap = DEFAULT_MAX_SCRAP;
    long mCreateRunningAverageNs = 0;
    long mBindRunningAverageNs = 0;
    }
    SparseArray<ScrapData> mScrap = new SparseArray<>();
    但存在mRecyclerPool的 ViewHolder 的数据信息会被重置掉,相当于 ViewHolder 是一个重创新建的一样,所以需要重新调用 onBindViewHolder 来绑定数据,所以尽量不要让ViewHolder进入这一层
    缓存优化
    进入RecyclerPool的ViewHolder会被重置,会从新执行bindViewHolder,所以从效率上来讲,很费性能。
    所以为了避免进入这一层缓存,可以在在第三层自定义缓存自己实现,也就是自定义mViewCacheExtension。在这里自己维护一个viewType对应View的SparseArray。这样可以避免因为多种type导致的holder重建。
    public abstract static class ViewCacheExtension {
    @Nullable
    public abstract View getViewForPositionAndType(@NonNull Recycler recycler, int position,int type);
    }
    注意getViewForPositionAndType返回的是view而不是ViewHolder,然后会通过view的layoutParams拿到ViewHolder。
    第二可以增大mCachedViews的缓存数量,改成你需要的量。

    --Recycler
    本文的最后一个模块recycler
Recycler 就是控制 RecyclerView 缓存的核心类。理解了它的工作过程,就可以弄明白 RecyclerView 如何回收 view,如何复用 view 的。
    在上一个章节,我们说过了 LayoutManager 其实就是 Recycler 的控制者,由 LayoutManager 来决定调用 Recycler关键方法的时机,直接来看看 LinearLayoutManager 的代码吧:
    首先来看最关键的部分 onLayoutChildren 方法,这个方法是在 RecyclerView 的 dispatchLayoutStep2 阶段调用的。
    onLayoutChildren 方法比较长,它核心的作用就是寻找一个锚点,为 RecyclerView 填充子 View。在 onLayoutChildren 调用的 fill 方法就是真正开始填充 layout 的方法。
    int fill(RecyclerView.Recycler recycler, LayoutState layoutState,RecyclerView.State state, boolean stopOnFocusable) {
    // max offset we should set is mFastScroll + available
    final int start = layoutState.mAvailable;
    int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
    LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
    while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
    layoutChunkResult.resetInternal();
    layoutChunk(recycler, state, layoutState, layoutChunkResult);
    if (layoutChunkResult.mFinished) {
    break;
    }
    if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
    layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
    if (layoutState.mAvailable < 0) {
    layoutState.mScrollingOffset += layoutState.mAvailable;
    }
    recycleByLayoutState(recycler, layoutState);
    }
    }
    return start - layoutState.mAvailable;
    }
    这就是省略了部分代码的 fill 方法,其中的 while 循环中,通过判断 LaytouState 中保存的状态来不断的通过 LayoutChunk 方法填充 view。所以接着再看 LayoutChunk 方法。
    void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {
    View view = layoutState.next(recycler);
    if (view == null) {
    // if we are laying out views in scrap, this may return null which means there is
    // no more items to layout.
    result.mFinished = true;
    return;
    }
    LayoutParams params = (LayoutParams) view.getLayoutParams();
    if (layoutState.mScrapList == null) {
    if (mShouldReverseLayout == (layoutState.mLayoutDirection
    == LayoutState.LAYOUT_START)) {
    addView(view);
    } else {
    addView(view, 0);
    }
    } else {
    if (mShouldReverseLayout == (layoutState.mLayoutDirection
    == LayoutState.LAYOUT_START)) {
    addDisappearingView(view);
    } else {
    addDisappearingView(view, 0);
    }
    }
    }
    以上就是 layoutChunk 的部分代码,可以看到通过 next 方法取出来 view ,并且通过 addView 添加到 RecyclerView 里面去,继续跟进next 方法,看看它是用什么方式和规则取出来 View 的。
    View next(RecyclerView.Recycler recycler) {
    if (mScrapList != null) {
    return nextViewFromScrapList();
    }
    final View view = recycler.getViewForPosition(mCurrentPosition);
    mCurrentPosition += mItemDirection;
    return view;
    }
    一路看到这里,我们的 Recycler 终于出现了...跟进 getViewForPosition 方法吧。
    在此之前可以先看看 Recycler 几个重要的成员变量,便于我们更好的认识 RecyclerView 的缓存结构:
    final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
    ArrayList<ViewHolder> mChangedScrap = null;
    final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
    private final List<ViewHolder>mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap);
    RecycledViewPool mRecyclerPool;
    private ViewCacheExtension mViewCacheExtension;
    提前说一下,RecyclerView 其实可以算作是四级缓存、mAttachedScrap 、mCachedViews 、mViewCacheExtension、mRecyclerPool 这四个对象就是作为每一级缓存的结构的。
    getViewForPosition 方法:
    View getViewForPosition(int position, boolean dryRun) {
    return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView;
    }
    跟进 tryGetViewHolderForPositionByDeadline方法,一段一段的阅读这个方法代码。
在文章之初我们就说过,RecyclerView 的缓存单元是 ViewHolder,
    这个tryGetViewHolderForPositionByDeadline 方法就完整的展示了如何在每个层级的缓存中,取出来 ViewHolder,下面我们一步步的分析一下:
    // 0) If there is a changed scrap, try to find from there
    if (mState.isPreLayout()) {
    holder = getChangedScrapViewForPosition(position);
    fromScrapOrHiddenOrCache = holder != null;
    }
    第一步,从 mChangedScrap 中尝试取出缓存的 ViewHolder ,若不存在,返回空。
    // 1) Find by position from scrap/hidden list/cache
    if (holder == null) {
    holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
    if (holder != null) {
    if (!validateViewHolderForOffsetPosition(holder)) {
    // recycle holder (and unscrap if relevant) since it can't be used
    if (!dryRun) {
    // we would like to recycle this but need to make sure it is not used by
    // animation logic etc.
    holder.addFlags(ViewHolder.FLAG_INVALID);
    if (holder.isScrap()) {
    removeDetachedView(holder.itemView, false);
    holder.unScrap();
    } else if (holder.wasReturnedFromScrap()) {
    holder.clearReturnedFromScrapFlag();
    }
    recycleViewHolderInternal(holder);
    }
    holder = null;
    } else {
    fromScrapOrHiddenOrCache = true;
    }
    }
    }
    如果在第一步发现没有缓存的 ViewHolder,则去 mAttachedScrap 中取,当然 mAttachedScrap 中没有怎么办呢,接着去 mHiddenViews 里面去找,如果还没有,
    继续从 mCachedViews 中取缓存的 ViewHolder ,这一系列操作是缓存层级的第二步。
    if (holder == null) {
    final int offsetPosition = mAdapterHelper.findPositionOffset(position);
    if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
    throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
    + "position " + position + "(offset:" + offsetPosition + ")."
    + "state:" + mState.getItemCount());
    }

    final int type = mAdapter.getItemViewType(offsetPosition);
    // 2) Find from scrap/cache via stable ids, if exists
    if (mAdapter.hasStableIds()) {
    holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
    type, dryRun);
    if (holder != null) {
    // update position
    holder.mPosition = offsetPosition;
    fromScrapOrHiddenOrCache = true;
    }
    }
    if (holder == null && mViewCacheExtension != null) {
    // We are NOT sending the offsetPosition because LayoutManager does not
    // know it.
    final View view = mViewCacheExtension
    .getViewForPositionAndType(this, position, type);
    if (view != null) {
    holder = getChildViewHolder(view);
    if (holder == null) {
    throw new IllegalArgumentException("getViewForPositionAndType returned"
    + " a view which does not have a ViewHolder");
    } else if (holder.shouldIgnore()) {
    throw new IllegalArgumentException("getViewForPositionAndType returned"
    + " a view that is ignored. You must call stopIgnoring before"
    + " returning this view.");
    }
    }
    }
    if (holder == null) { // fallback to pool
    if (DEBUG) {
    Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
    + position + ") fetching from shared pool");
    }
    holder = getRecycledViewPool().getRecycledView(type);
    if (holder != null) {
    holder.resetInternal();
    if (FORCE_INVALIDATE_DISPLAY_LIST) {
    invalidateDisplayListInt(holder);
    }
    }
    }
    if (holder == null) {
    long start = getNanoTime();
    if (deadlineNs != FOREVER_NS
    && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
    // abort - we have a deadline we can't meet
    return null;
    }
    holder = mAdapter.createViewHolder(RecyclerView.this, type);
    if (ALLOW_THREAD_GAP_WORK) {
    // only bother finding nested RV if prefetching
    RecyclerView innerView = findNestedRecyclerView(holder.itemView);
    if (innerView != null) {
    holder.mNestedRecyclerView = new WeakReference<>(innerView);
    }
    }
    long end = getNanoTime();
    mRecyclerPool.factorInCreateTime(type, end - start);
    if (DEBUG) {
    Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
    }
    }
    }
    在 getScrapOrCachedViewForId 方法中,根据 id 依次在 mAttachedScrap 、mCachedViews 集合中寻找缓存的 ViewHolder,如果都不存在,
    则在 ViewCacheExtension 对象中寻找缓存,ViewCacheExtension 这个类需要使用者通过 setViewCacheExtension 方法传入,RecyclerView 自身并不会实现它,
    一般正常的使用也用不到。
    缓存层级的第三步,我们就分析完毕了,来看最后一步。
    if (holder == null) { // fallback to pool
    if (DEBUG) {
    Log.d(TAG, "tryGetViewHolderForPositionByDeadline("
    + position + ") fetching from shared pool");
    }
    holder = getRecycledViewPool().getRecycledView(type);
    if (holder != null) {
    holder.resetInternal();
    if (FORCE_INVALIDATE_DISPLAY_LIST) {
    invalidateDisplayListInt(holder);
    }
    }
    }
    这段代码是从 RecycledViewPool 中取根据 type 取 ViewHolder,对于 RecycledViewPool 下面多说几句:
    static class ScrapData {
    ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
    int mMaxScrap = DEFAULT_MAX_SCRAP;
    long mCreateRunningAverageNs = 0;
    long mBindRunningAverageNs = 0;
    }
    SparseArray<ScrapData> mScrap = new SparseArray<>();
    从这里我们知道,RecycledViewPool 其实是一个 SparseArray 保存 ScrapData 对象的结构。根据 type 缓存 ViewHolder,每个 type,默认最多保存5个 ViewHolder。
    上面提到的 mCachedViews 这个集合默认最大值是 2 。
    RecycledViewPool 可以由多个 ReyclerView 共用。
    RecycledViewPool 就是缓存结构中的第四级缓存了,如果 RecycledViewPool 中依然没有缓存的 ViewHolder 怎么办呢?
    if (holder == null) {
    long start = getNanoTime();
    if (deadlineNs != FOREVER_NS
    && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
    // abort - we have a deadline we can't meet
    return null;
    }
    holder = mAdapter.createViewHolder(RecyclerView.this, type);
    if (ALLOW_THREAD_GAP_WORK) {
    // only bother finding nested RV if prefetching
    RecyclerView innerView = findNestedRecyclerView(holder.itemView);
    if (innerView != null) {
    holder.mNestedRecyclerView = new WeakReference<>(innerView);
    }
    }
    long end = getNanoTime();
    mRecyclerPool.factorInCreateTime(type, end - start);
    if (DEBUG) {
    Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder");
    }
    }
    这个时候没有办法,就只能调用 mAdapter.createViewHolder(RecyclerView.this, type),来创建一个 ViewHolder 了。
    到此我们就知道一个 ViewHolder 是如何层层从缓存中取出的了。
    写在后面:
本文从源码的角度研究了 RecyclerView 的主要模块和功能,但是 RecyclerView 本身是很复杂的,要考虑到非常多的情况,光是各种状态的记录就让人看得很迷,
    这种复杂度的控件真不是一般程序员可以搞定的。不过我们不必深究每一块细节,将大致的流程梳理在心,也肯定会有收获和启发。未来打算总结下 RecyclerView 可能发生的一些复用问题。

    -->ItemDecoration
    public void addItemDecoration(ItemDecoration decor, int index) {
    if (mLayout != null) {
    mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or"
    + " layout");
    }
    if (mItemDecorations.isEmpty()) {
    setWillNotDraw(false);
    }
    if (index < 0) {
    mItemDecorations.add(decor);
    } else {
    // 指定添加分割线在集合中的索引
    mItemDecorations.add(index, decor);
    }
    markItemDecorInsetsDirty();
    // 重新请求 View 的测量、布局、绘制
    requestLayout();
    }
    mItemDecorations 是一个 ArrayList,我们将 ItemDecoration 也就是分割线对象,添加到其中。接着我们看下 markItemDecorInsetsDirty 这个方法做了些什么。
    void markItemDecorInsetsDirty() {
    final int childCount = mChildHelper.getUnfilteredChildCount();
    for (int i = 0; i < childCount; i++) {
    final View child = mChildHelper.getUnfilteredChildAt(i);
    ((LayoutParams) child.getLayoutParams()).mInsetsDirty = true;
    }
    mRecycler.markItemDecorInsetsDirty();
    }
    这个方法首先遍历了 RecyclerView 和 LayoutManager 的所有子 View,将其子 View 的 LayoutParams 中的 mInsetsDirty 属性置为 true。接着调用了 mRecycler.markItemDecorInsetsDirty(),Recycler 是 RecyclerView 的一个内部类,就是它管理着 RecyclerView 的复用逻辑。这个我们一会再细谈。
    void markItemDecorInsetsDirty() {
    final int cachedCount = mCachedViews.size();
    for (int i = 0; i < cachedCount; i++) {
    final ViewHolder holder = mCachedViews.get(i);
    LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams();
    if (layoutParams != null) {
    layoutParams.mInsetsDirty = true;
    }
    }
    }
    mCachedViews 见名知意,也就是 RecyclerView 缓存的集合,相信你也看到了,RecyclerView 的缓存单位是 ViewHolder。我们在 ViewHolder 中取出 itemView,然后获得 LayoutParams,将其 mInsetsDirty 字段一样置为 true。
    mInsetsDirty 字段的作用其实是一种优化性能的缓存策略,添加分割线对象时,无论是 RecyclerView 的子 view,还是缓存的 view,都将其置为 true,接着就调用了 requestLayout 方法。
    这里简单说一下 requestLayout 方法用一种责任链的方式,层层向上传递,最后传递到 ViewRootImpl,然后重新调用 view 的 measure、layout、draw 方法来展示布局。
    我们在 RecyclerView 中搜索 mItemDecorations 集合,看看他是在什么时刻操作 ItemDecoration 这个分割线对象的。
    在 onDraw 中:
    @Override
    public void onDraw(Canvas c) {
    super.onDraw(c);

    final int count = mItemDecorations.size();
    for (int i = 0; i < count; i++) {
    mItemDecorations.get(i).onDraw(c, this, mState);
    }
    }
    在 draw 方法中:
    @Override
    public void draw(Canvas c) {
    super.draw(c);

    final int count = mItemDecorations.size();
    for (int i = 0; i < count; i++) {
    mItemDecorations.get(i).onDrawOver(c, this, mState);
    }
    }
    可以看到在 View 的以上两个方法中,分别调用了 ItemDecoration 对象的 onDraw onDrawOver 方法。
    这两个抽象方法,由我们继承 ItemDecoration 来自己实现,他们区别就是 onDraw 在 item view 绘制之前调用,onDrawOver 在 item view 绘制之后调用。
    所以绘制顺序就是 Decoration 的 onDraw,ItemView的 onDraw,Decoration 的 onDrawOver。
    (好像越写越多...收不住了...)
    我们在 onDraw 和 onDrawOver 方法中就可以绘制 drawable 对象了。此时分割线就展现出来了。还记得刚才的 mInsetsDirty 字段吗?在添加分割线的时候,无论是 RecyclerView 子 View,还是缓存中的 View,其 LayoutParams 中的 mInsetsDirty 属性,都被置为 true。 我们来解释一下这个字段的作用:
    Rect getItemDecorInsetsForChild(View child) {
    final LayoutParams lp = (LayoutParams) child.getLayoutParams();
    if (!lp.mInsetsDirty) {
    // 当 mInsetsDirty 为 false,说明 mDecorInsets 缓存可用
    return lp.mDecorInsets;
    }

    if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid())) {
    // changed/invalid items should not be updated until they are rebound.
    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;
    }
    来解释一下这段代码,首先 getItemDecorInsetsForChild 方法是在 RecyclerView 进行 measureChild 时调用的。目的就是为了取出 RecyclerView 的 ChildView 中的分割线属性 --- 在 LayoutParams 中缓存的 mDecorInsets 。而 mDecorInsets 就是 Rect 对象, 其保存记录的是所有添加分割线需要的空间累加的总和,由分割线的 getItemOffsets 方法影响。
    最后在 measureChild 方法里,将分割线 ItemDecoration 的尺寸加入到 itemView 的 padding 中。
    但是大家都知道缓存并不是总是可用的,mInsetsDirty 这个 boolean 字段来记录它的时效性,当 mInsetsDirty 为 false 时,说明缓存可用,直接取出可以,当 mInsetsDirty 为 true 时,说明缓存的分割线属性就需要重新计算了。
    到此,关于 RecyclerView 添加分割线 ItemDecoration 的源码分析

    -->ItemAnimator

    如果有人问我,在什么情况下你绝对会选择 RecyclerView,而不是 ListView?如果需求对 Item 的动画有一定要求,这绝对是我选择 RecyclerView 的重要原因之一。ListView 如果要做 Item 的增删动画,那可要费很大劲儿,而 RecyclerView 自身对动画就有很好的支持。
    public void setItemAnimator(ItemAnimator animator) {
    if (mItemAnimator != null) {
    mItemAnimator.endAnimations();
    mItemAnimator.setListener(null);
    }
    mItemAnimator = animator;
    if (mItemAnimator != null) {
    mItemAnimator.setListener(mItemAnimatorListener);
    }
    }
    这个方法就是为 RecyclerView 设置动画的入口,逻辑就是清除旧的 Listener,设置新的 Listener。这个没什么可多说的,我们这次直接来看看 ItemAnimator 这个类。
    当我第一次粗略的看 RecyclerView 的源码之时,感觉这也太多了吧,一万两千多行,这得多复杂啊。后来再看看,才知道,RecyclerView 是把
    ItemAnimator 、LayoutManager 等等这些模块都当内部类写在了一块。。。我不知道这样设计的目的是啥,除了写着方便之外可能找不到其他理由了,so... 可能是 google 的程序员偷个懒吧 - -
    来看 ItemAnimator 类:
    public @NonNull ItemHolderInfo recordPreLayoutInformation(@NonNull State state,
    @NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags,
    @NonNull List<Object> payloads) {
    return obtainHolderInfo().setFrom(viewHolder);
    }
    public @NonNull ItemHolderInfo recordPostLayoutInformation(@NonNull State state,
    @NonNull ViewHolder viewHolder) {
    return obtainHolderInfo().setFrom(viewHolder);
    }
    这两个函数看命名也能猜一个大概,其目的就是为了记录在 RecyclerView 布局之前/之后,必要的一些 layout 信息保存在 ItemHolderInfo 中,ItemHolderInfo 这个类就是用来记录当前 ItemView 的位置信息
    recordPreLayoutInformation 来记录 layout 之前的状态信息,这个方法在 dispatchLayoutStep1 之中调用。
    dispatchLayoutStep1这个方法做了什么呢,来看看注释就一目了然:
    /**
    * The first step of a layout where we;
    * - process adapter updates
    * - decide which animation should run
    * - save information about current views
    * - If necessary, run predictive layout and save its information
    */
    这是布局的第一步:
进行 adapter 布局的更新,决定执行哪个动画,保存当前 view 的信息,如果有必要,运行 predictive layout。
    相同的 recordPostLayoutInformation 方法来记录 layout 过程完成时,ItemView 的信息。
    它在 dispatchLayoutStep3 方法中调用,dispatchLayoutStep3 方法作用:
    /**
    * The final step of the layout where we save the information about views for animations,
    * trigger animations and do any necessary cleanup.
    */
    layout 的最后一个步骤,保存 view 动画的信息,执行动画,状态清理。当然也有 dispatchLayoutStep2 方法,他们三个方法依次在 onLayout 方法的 dispatchLayout方法调用。
    dispatchLayout 方法目的是 layout RecyclerView 的 childview,并且记录动画执行的过程、变更。
    现在知道了这两个 API 的作用就是在 layout 前后记录 itemview 动画的状态,保存在 ItemHolderInfo 中,我们继续寻找执行动画的 API。
    • animateDisappearance
当 ViewHolder 从 RecyclerView 的 layout 中移除时,调用
    • animateAppearance
当 ViewHolder 添加进 RecyclerView 时,调用
    • animatePersistence
当 ViewHolder 已经添加进 layout 还未移除时,调用
    • animateChange
当 ViewHolder 已经添加进 layout 还未移除,并且调用了 notifyDataSetChanged 时,调用。
    以上四个 api 就是为 RecyclerView 执行动画的。
    调用的时机和方式是如何的呢?
    /**
    * The callback to convert view info diffs into animations.
    */
    private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
    new ViewInfoStore.ProcessCallback() {
    @Override
    public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
    @Nullable ItemHolderInfo postInfo) {
    mRecycler.unscrapView(viewHolder);
    animateDisappearance(viewHolder, info, postInfo);
    }
    @Override
    public void processAppeared(ViewHolder viewHolder,
    ItemHolderInfo preInfo, ItemHolderInfo info) {
    animateAppearance(viewHolder, preInfo, info);
    }

    @Override
    public void processPersistent(ViewHolder viewHolder,
    @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
    viewHolder.setIsRecyclable(false);
    if (mDataSetHasChangedAfterLayout) {
    // since it was rebound, use change instead as we'll be mapping them from
    // stable ids. If stable ids were false, we would not be running any
    // animations
    if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo,
    postInfo)) {
    postAnimationRunner();
    }
    } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) {
    postAnimationRunner();
    }
    }
    @Override
    public void unused(ViewHolder viewHolder) {
    mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler);
    }
    };
    mViewInfoProcessCallback 是一个匿名内部类,在其回调方法中,分别执行了以上四个关于动画的 api。
    继续跟进,看看mViewInfoProcessCallback 这个接口是什么时候被调用执行的:
    // Step 4: Process view info lists and trigger animations
    mViewInfoStore.process(mViewInfoProcessCallback);
    执行触发方法的位置就在我们刚才提到的 dispatchLayoutStep3方法中,去根据一些保存的 flag 状态去触发动画。

    -->LayoutManager

    与其他绑定 adapter 展示数据的控件,比如 ListView、GrideView 相比,RecyclerView 允许自定义规则去放置子 view,这个规则的控制者就是 LayoutManager。一个 RecyclerView 如果想展示内容,就必须设置一个 LayoutManager。
    我们按照惯例来看设置 LayoutManager 的入口:
    public void setLayoutManager(LayoutManager layout) {
    if (layout == mLayout) {
    return;
    }
    // 停止滑动
    stopScroll();
    // TODO We should do this switch a dispatchLayout pass and animate children. There is a good
    // chance that LayoutManagers will re-use views.
    if (mLayout != null) {
    // end all running animations
    if (mItemAnimator != null) {
    mItemAnimator.endAnimations();
    }
    // 移除并回收视图
    mLayout.removeAndRecycleAllViews(mRecycler);
    // 回收废弃视图
    mLayout.removeAndRecycleScrapInt(mRecycler);
    mRecycler.clear();

    if (mIsAttached) {
    mLayout.dispatchDetachedFromWindow(this, mRecycler);
    }
    mLayout.setRecyclerView(null);
    mLayout = null;
    } else {
    mRecycler.clear();
    }
    // this is just a defensive measure for faulty item animators.
    mChildHelper.removeAllViewsUnfiltered();
    mLayout = layout;
    if (layout != null) {
    if (layout.mRecyclerView != null) {
    throw new IllegalArgumentException("LayoutManager " + layout +
    " is already attached to a RecyclerView: " + layout.mRecyclerView);
    }
    mLayout.setRecyclerView(this);
    if (mIsAttached) {
    mLayout.dispatchAttachedToWindow(this);
    }
    }
    mRecycler.updateViewCacheSize();
    requestLayout();
    }
    这段代码主要做了一下几件事:
当之前设置过 LayoutManager 时,移除之前的视图,并缓存视图在 Recycler 中,将新的 mLayout 对象与 RecyclerView 绑定,更新缓存 View 的数量。最后去调用 requestLayout ,重新请求 measure、layout、draw。
    关于 RecyclerView 的缓存我们一会再研究,先看看设置的 LayoutManager 是在何时何处发挥作用的吧:
    LayoutManager 作为 RecyclerView 的一个抽象内部类,大概有3000行代码,方法数还很多,看着头都大了。用有限的时间去了解每一个方法的作用显然不现实。LayoutManager 的作用就是为 RecyclerView 放置子 view,所以我直接去定位 RecyclerView 的 onLayout 和 onMeasure 方法,研究一下 LayoutManager 的一些关键函数的作用。
    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
    if (mLayout == null) {
    defaultOnMeasure(widthSpec, heightSpec);
    return;
    }
    if (mLayout.mAutoMeasure) {
    final int widthMode = MeasureSpec.getMode(widthSpec);
    final int heightMode = MeasureSpec.getMode(heightSpec);
    final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
    && heightMode == MeasureSpec.EXACTLY;
    mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
    if (skipMeasure || mAdapter == null) {
    return;
    }
    if (mState.mLayoutStep == State.STEP_START) {
    dispatchLayoutStep1();
    }
    // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
    // consistency
    mLayout.setMeasureSpecs(widthSpec, heightSpec);
    mState.mIsMeasuring = true;
    dispatchLayoutStep2();

    // now we can get the width and height from the children.
    mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);

    // if RecyclerView has non-exact width and height and if there is at least one child
    // which also has non-exact width & height, we have to re-measure.
    if (mLayout.shouldMeasureTwice()) {
    mLayout.setMeasureSpecs(
    MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
    mState.mIsMeasuring = true;
    dispatchLayoutStep2();
    // now we can get the width and height from the children.
    mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
    }
    } else {
    // 省略非自动测量的情况
    }
    }
    来分析一下 onMeasure 方法,mAutoMeasure 字段用来标记是否使用 RecyclerView 的默认规则进行自动测量,否则就必须在 LayoutManager 中自己实现 onMeasure 来进行测量。LinearLayoutManager 的 mAutoMeasure 字段属性就被设置成为了 true。
    所以我们重点来看mAutoMeasure为 true 时,测量的规则。
    当 RecyclerView 的 MeasureSpec 为 MeasureSpec.EXACTLY时,这个时候可以直接确定 RecyclerView 的宽高,所以 return 退出测量。当 RecyclerView 的宽高为不为 EXACTLY 时,首先进行的测量步骤就是 dispatchLayoutStep1,这个我们在分析动画源码的时候提到过。dispatchLayoutStep1 的作用总结起来就是记录 layout 之前,view 的信息。
    接着继续调用了 dispatchLayoutStep2方法:
    /**
    * The second layout step where we do the actual layout of the views for the final state.
    * This step might be run multiple times if necessary (e.g. measure).
    */
    private void dispatchLayoutStep2() {
    ...
    // Step 2: Run layout
    ...
    mLayout.onLayoutChildren(mRecycler, mState);
    ...
    mState.mLayoutStep = State.STEP_ANIMATIONS;
    }
    dispatchLayoutStep2 比较关键的就是以上代码展示的这两步,onLayoutChildren 这个函数由 LayoutManager 实现,来规定放置子 view 的算法,寻找锚点填充 view,锚点的寻找和填充 view 的方式,这里就不细说了。因为篇幅实在是太长。具体可以直接去看 LinearLayoutManager 的实现方式。
    第二步就是将 mState.mLayoutStep 置为 State.STEP_ANIMATIONS,刚才我们忘记说 mLayoutStep 这个属性了,从它的命名就知道它是来标记 layout 这个过程进行到哪一步了。在 dispatchLayoutStep1 中 mState.mLayoutStep 被置为 State.STEP_LAYOUT,记录 layout 的步骤是什么原因呢?来看看 RecyclerView 的 onLayout 方法:
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
    TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
    dispatchLayout();
    TraceCompat.endSection();
    mFirstLayoutComplete = true;
    }
    跟进 dispatchLayout
    void dispatchLayout() {
    if (mAdapter == null) {
    Log.e(TAG, "No adapter attached; skipping layout");
    // leave the state in START
    return;
    }
    if (mLayout == null) {
    Log.e(TAG, "No layout manager attached; skipping layout");
    // leave the state in START
    return;
    }
    mState.mIsMeasuring = false;
    if (mState.mLayoutStep == State.STEP_START) {
    dispatchLayoutStep1();
    mLayout.setExactMeasureSpecsFrom(this);
    dispatchLayoutStep2();
    } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() ||
    mLayout.getHeight() != getHeight()) {
    // First 2 steps are done in onMeasure but looks like we have to run again due to
    // changed size.
    mLayout.setExactMeasureSpecsFrom(this);
    dispatchLayoutStep2();
    } else {
    // always make sure we sync them (to ensure mode is exact)
    mLayout.setExactMeasureSpecsFrom(this);
    }
    dispatchLayoutStep3();
    }
    看到 mState.mLayoutStep 的作用了吧,当我们在 onMeasure 方法中已经调用过 dispatchLayoutStep1 、 dispatchLayoutStep2 时,在 onLayout 方法中只会调用 dispatchLayoutStep3,dispatchLayoutStep3方法我们在动画部分的源码讲解过。
    回想一下,什么情况下会在 onMeasure 方法中直接调用 dispatchLayoutStep1 、 dispatchLayoutStep2 ? 就是 RecyclerView 的 MeasureSpec 不为 EXACTLY 时,这个情况下 RecyclerView 不能自己确定自身的宽高,只能在测量、布局了子 view 才能确定自己的宽高。所以在 onMeasure 的时候就调用了 dispatchLayoutStep1 、 dispatchLayoutStep2 ,在 onLayout 仅仅调用 dispatchLayoutStep3 方法就可以了。

    如果文字表述的不够清晰,这里来一张图:

     
    onMeasure/onLayout

    总结一下 LayoutManager 的作用:

    • 协助 RecyclerView 完成 onMeasure 过程
    • 通过 onLayoutChildren 完成对子 view 的布局
    • 滚动子视图
    • 滚动过程中判断何时添加 view ,何时回收 view,也就是对缓存时机的判断。
  • 相关阅读:
    修改系统策略 作为服务登录
    PlayStation Suite(PSS)快速预览
    Python异常处理
    误操作把/bin目录移动到其他目录下的恢复方法
    SQL Prompt 自动过期反复激活防失效注册后隔一段时间莫名无法使用的处理
    [转帖]SQL SERVER 2005 同步复制技术
    动网论坛修改笔记
    网线的做法
    风讯修改记录
    远程桌面中调出任务管理器
  • 原文地址:https://www.cnblogs.com/awkflf11/p/16193325.html
Copyright © 2020-2023  润新知