RecyclerView刷新机制

掘金博客记录

  • adapter.notifyDataSetChanaged() 引起的刷新

    我们假设 recycleView 的初始状态是没有数据的,然后往数据源中加入数据后,调用

    adapter.notifyDataSetChanged() 来引起RecyclerView 的刷新:

    1
    2
    data.addAll(data)
    adapter.notifyDataSetChanged()

    adapter.notifyDataSetChanged() 时,会引起 recycleView 重新布局(requestLayout).因此从 onLayout() 方法开始:

    1
    2
    3
    4
    5
    6
    7
    @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():

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    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();
    }

    缩写为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    void dispatchLayout() {
    ...
    if (mState.mLayoutStep == State.STEP_START) {
    dispatchLayoutStep1();
    dispatchLayoutStep2();
    } else if (数据变化 || 布局变化) {
    dispatchLayoutStep2();
    }
    dispatchLayoutStep3();
    }

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

    1
    2
    3
    STEP_START---->dispatchLayoutStep1()
    STEP_LAYOUT---->dispatchLayoutStep2()
    STEP_ANIMATIONS---->dispatchLayoutStep2(),dispatchLayoutStep3()

    第一步:STEP_START

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    /**
    * The first step of a layout where we;
    * - process adapter updates 更新adapter
    * - decide which animation should run 确定哪个动画应该运行
    * - save information about current views 保存当前views的信息
    * - If necessary, run predictive layout and save its information
    */
    private void dispatchLayoutStep1() {
    .....
    }

    第二步:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    /**
    确定view的真正状态
    * The second layout step where we do the actual layout of the views for the final state.
    这步可能会执行多次如果necessary
    * This step might be run multiple times if necessary (e.g. measure).
    */
    private void dispatchLayoutStep2() {
    startInterceptRequestLayout();
    onEnterLayoutOrScroll();
    mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
    mAdapterHelper.consumeUpdatesInOnePass();
    //设置好初始状态
    mState.mItemCount = mAdapter.getItemCount();
    mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

    // Step 2: Run layout 调用布局管理器布局
    mState.mInPreLayout = false;
    mLayout.onLayoutChildren(mRecycler, mState);

    mState.mStructureChanged = false;
    mPendingSavedState = null;

    // onLayoutChildren may have caused client code to disable item animations; re-check
    mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
    mState.mLayoutStep = State.STEP_ANIMATIONS;//接下来执行布局的第三步
    onExitLayoutOrScroll();
    stopInterceptRequestLayout(false);
    }

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

  • LinearLayoutManager.onLayoutChildren()

    这个方法比较长,就不展示具体源码。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @Override
    public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
    // layout algorithm: 布局算法
    1.检查children 和其它变量,找到锚坐标和锚
    // 1) by checking children and other variables, find an anchor coordinate and an anchor
    // item position. item位置
    2.开始填充,从底部开始
    // 2) fill towards start, stacking from bottom
    3.填充
    // 3) fill towards end, stacking from top
    4.滚动满足要求像从底部填充一样
    // 4) scroll to fulfill requirements like stack from bottom.
    // create layout state
    • 确定锚点View

      锚点View的确定通过: updateAnchorInfoForLayout

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      // calculate anchor position and coordinate
      updateAnchorInfoForLayout(recycler, state, mAnchorInfo);

      private boolean updateAnchorFromChildren(RecyclerView.Recycler recycler,
      RecyclerView.State state, AnchorInfo anchorInfo) {
      ....
      View referenceChild = anchorInfo.mLayoutFromEnd
      //如果是从end(尾部)位置开始布局,那就找最接近end的那个位置的view作为锚点View
      ? findReferenceChildClosestToEnd(recycler, state)
      ////如果是从start(尾部)位置开始布局,那就找最接近start的那个位置的view作为锚点View
      : findReferenceChildClosestToStart(recycler, state);
      }

AnchorInfo 最重要的两个属性是; mCoordinatemPosition. 找到锚点 View 后就会通过anchorInfo.assignFromView() 方法来设置这两个属性:

1
2
3
4
5
6
7
8
9
10
public void assignFromView(View child, int position) {
if (mLayoutFromEnd) {
mCoordinate = mOrientationHelper.getDecoratedEnd(child)
+ mOrientationHelper.getTotalSpaceChange();
} else {
mCoordinate = mOrientationHelper.getDecoratedStart(child);
}

mPosition = position;
}
1
2
mCoordinate: 就是锚点View的 Y(x) 坐标去掉 RecycleView 的 padding.
mPosition: 其实就是 锚点View的位置

当确定好 AnchorInfo 后,需要根据 AnchorInfo 来确定 RecycleView 当前可用于布局的空间,然后来摆放子View。以布局方向为 start to end 正常方向为例,这里的锚点View其实就是 RecyclerView最顶部的 View:

1
2
3
4
5
6
7
8
9
10
// fill towards end  (1)
updateLayoutStateToFillEnd(mAnchorInfo); //确定AnchorView到RecyclerView的底部的布局可用空间
...
fill(recycler, mLayoutState, state, false); //填充view, 从 AnchorView 到RecyclerView的底部
endOffset = mLayoutState.mOffset;

// fill towards start (2)
updateLayoutStateToFillStart(mAnchorInfo); //确定AnchorView到RecyclerView的顶部的布局可用空间
...
fill(recycler, mLayoutState, state, false); //填充view,从 AnchorView 到RecyclerView的顶部

上面我标注了(1)(2),1次布局是由这两部分组成的,具体如下图:

1545640614074

fill towards end

确定可用布局空间

fill 之前,需要先确定 从锚点ViewRecyclerView 底部有多少可用空间。是通过 updateLayoutStateToFillEnd 方法:

1
2
3
4
5
6
7
8
9
10
updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate);

void updateLayoutStateToFillEnd(int itemPosition, int offset) {
mLayoutState.mAvailable = mOrientationHelper.getEndAfterPadding() - offset;
...
mLayoutState.mCurrentPosition = itemPosition;
mLayoutState.mLayoutDirection = LayoutState.LAYOUT_END;
mLayoutState.mOffset = offset;
mLayoutState.mScrollingOffset = LayoutState.SCROLLING_OFFSET_NaN;
}

mLayoutStateLinearLayoutmanager 用来保存布局状态的一个对象。 mLayoutState.mAvailable 就是用来表示 有多少空间可用布局。mOrientationHelper.getEndAfterPadding() - offset 其实大致可以理解为RecycleView 的高度。所以这里可用布局空间 mLayoutState.mAvailable 就是 ReycleView 的高度。

摆放子View

接下来继续看 LinearLayoutManager.fill() 方法,这个方法是布局的核心方法,是用来向 RecycleView 中添加子View的方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
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;//可用高度就是RecyclerView的高度
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// TODO ugly bug fix. should not happen
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
//保存布局一个child view后的结果
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
//有剩余空间的话,就一直添加 childView
layoutChunkResult.resetInternal();
if (VERBOSE_TRACING) {
TraceCompat.beginSection("LLM LayoutChunk");
}
//布局子View的核心方法
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (VERBOSE_TRACING) {
TraceCompat.endSection();
}
if (layoutChunkResult.mFinished) {
break;
}
//一次layoutchunk 消耗了多少空间
layoutState.mOffset += layoutChunkResult.mConsumed * layoutState.mLayoutDirection;
/**
* Consume the available space if:
* * layoutChunk did not request to be ignored
* * OR we are laying out scrap children
* * OR we are not doing pre-layout
*/
回收可用空间....

return start - layoutState.mAvailable;
}

这个方法的核心就是调用 layoutChunk() 来不断消耗layoutState.mAvailable 直到消耗完毕,继续看下 layoutChunk() 方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler); //这个方法会向 recycler view 要一个holder
...
if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { //根据布局方向,添加到不同的位置
addView(view); //添加到 RecyclerView 中
} else {
addView(view, 0);
}
measureChildWithMargins(view, 0, 0); //调用view的measure

...measure后确定布局参数 left/top/right/bottom

layoutDecoratedWithMargins(view, left, top, right, bottom); //调用view的layout
...
}

到这里其实就完成了上面的 fill towards end:

1
2
3
updateLayoutStateToFillEnd(mAnchorInfo)// 确定布局可用空间

fill(recycler,mLayoutState,state,false)// 填充View

fill towards start 就是从 锚点ViewRecycleView 顶部来摆放子View,具体逻辑类似 fill towards end.

RecyclerView 滑动时的刷新逻辑

在不加载新的数据情况下, RecycleView 在滑动时是如何展示 子View的,即下面这种状态:

1545643914261

下面就来分析一个 3,4 和 12,13是如何展示的。

RecycleView 在 OnTouchEvent 对滑动事件做了监听,然后派发到 scrollStep() 方法:

-------------本文结束感谢您的阅读-------------