RecyclerView刷新机制

RecyclerView刷新机制

前面分析了
本文继续来看一下RecyclerView是如何完成UI的刷新以及在滑动时子View的添加逻辑

本文会从源码分析两件事 :

  1. adapter.notifyXXX()时RecyclerView的UI刷新的逻辑,即子View是如何添加到RecyclerView中的。
  2. 在数据存在的情况下,滑动RecyclerView子View是如何添加到RecyclerView并滑动的。

本文不会涉及到RecyclerView的动画,动画的实现会专门在一篇文章中分析。

adapter.notifyDataSetChanged()引起的刷新

我们假设RecyclerView在初始状态是没有数据的,然后往数据源中加入数据后,调用adapter.notifyDataSetChanged()来引起RecyclerView的刷新:

data.addAll(datas)
adapter.notifyDataSetChanged()

用图描述就是下面两个状态的转换:

adapter.notifyDataSetChanged.png

接下来就来分析这个变化的源码,在上一篇文章中已经解释过,adapter.notifyDataSetChanged()时,会引起RecyclerView重新布局(requestLayout),RecyclerViewonMeasure就不看了,核心逻辑不在这里。因此从onLayout()方法开始看:

RecyclerView.onLayout

这个方法直接调用了dispatchLayout:

void dispatchLayout() {
    ...
    if (mState.mLayoutStep == State.STEP_START) {
        dispatchLayoutStep1();
        dispatchLayoutStep2();
    } else if (数据变化 || 布局变化) {
        dispatchLayoutStep2();
    }
    dispatchLayoutStep3();
}

上面我裁剪掉了一些代码,可以看到整个布局过程总共分为3步, 下面是这3步对应的方法:

STEP_START ->  dispatchLayoutStep1()
STEP_LAYOUT -> dispatchLayoutStep2()
STEP_ANIMATIONS -> dispatchLayoutStep2(), dispatchLayoutStep3()

第一步STEP_START主要是来存储当前子View的状态并确定是否要执行动画。这一步就不细看了。 而第3步STEP_ANIMATIONS是来执行动画的,本文也不分析了,本文主要来看一下第二步STEP_LAYOUT,即dispatchLayoutStep2():

dispatchLayoutStep2()

先来看一下这个方法的大致执行逻辑:

private void dispatchLayoutStep2() {  
    startInterceptRequestLayout(); //方法执行期间不能重入
    ...
    //设置好初始状态
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
    mState.mInPreLayout = false;

    mLayout.onLayoutChildren(mRecycler, mState); //调用布局管理器去布局

    mState.mStructureChanged = false;
    mPendingSavedState = null;
    ...
    mState.mLayoutStep = State.STEP_ANIMATIONS; //接下来执行布局的第三步

    stopInterceptRequestLayout(false);
}

这里有一个mState,它是一个RecyclerView.State对象。顾名思义它是用来保存RecyclerView状态的一个对象,主要是用在LayoutManager、Adapter等组件之间共享RecyclerView状态的。可以看到这个方法将布局的工作交给了mLayout。这里它的实例是LinearLayoutManager,因此接下来看一下LinearLayoutManager.onLayoutChildren():

LinearLayoutManager.onLayoutChildren()

这个方法也挺长的,就不展示具体源码了。不过布局逻辑还是很简单的:

  1. 确定锚点(Anchor)View, 设置好AnchorInfo
  2. 根据锚点View确定有多少布局空间mLayoutState.mAvailable可用
  3. 根据当前设置的LinearLayoutManager的方向开始摆放子View

接下来就从源码来看这三步。

确定锚点View

锚点View大部分是通过updateAnchorFromChildren方法确定的,这个方法主要是获取一个View,把它的信息设置到AnchorInfo中 :

mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout   // 即和你是否在 manifest中设置了布局 rtl 有关

private boolean updateAnchorFromChildren(RecyclerView.Recycler recycler, RecyclerView.State state, AnchorInfo anchorInfo) {
    ...
    View referenceChild = anchorInfo.mLayoutFromEnd
            ? find