0%

Android控件-RecyclerView

RecyclerView 是谷歌官方推出的控件,可以完全取代listView,根据不同的layoutManager实现不同的显示效果。这里 有一个谷歌的RecyclerView视频介绍 ,里面提到ListView的主要问题在于,它只是知道数据变了却不知道哪里变了,因此难以设置动画, RecyclerView 则很好的解决了这个问题。然后是listview生成view的时候还需要开发者去写contentView相关的代码,很麻烦,RecyclerView 中则不需要。

这里打算按照操作顺序来阅读。对RecyclerView的基本使用是:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
RecyclerView recyclerView = new RecyclerView(this);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(new RecyclerView.Adapter() {
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
TextView textView = new TextView(parent.getContext());
textView.setText("123");
return new RecyclerView.ViewHolder(textView) {
};
}

@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {

}

@Override
public int getItemCount() {
return 3;
}
});
  1. 首先是调用了构造函数,构造函数里面初始化了一些变量,这个没什么好说的,

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public RecyclerView(...) {
    super(context, attrs, defStyle);
    ...
    setWillNotDraw(getOverScrollMode() == View.OVER_SCROLL_NEVER);
    mItemAnimator.setListener(mItemAnimatorListener);
    initAdapterManager();
    initChildrenHelper();
    ...
    }

    其中,AdapterHelper主要用来处理Adapter的操作,入队,更新等并且通过callback回调回RecyclerView来。它还定义了一个内部类UpdateOp,用来封装数据的增删改和移动的操作,并且维护了一个UpdateOp的数组。
    ChildHelper则主要帮助RecyclerView来管理子view,它内部维护了一个 List mHiddenViews 数组。

  2. 然后是setLayoutManager:

    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
    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) {
    ...
    } 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.exceptionLabel());
    }
    mLayout.setRecyclerView(this);
    if (mIsAttached) {
    mLayout.dispatchAttachedToWindow(this);
    }
    }
    mRecycler.updateViewCacheSize();
    requestLayout();
    }

    这里就是把 mLayout 赋值给 RecyclerView ,同时也把 RecyclerView 的引用交给 mLayout 。需要注意的是如果这个时候RecyclerView已经attach到窗口了,会再分发一次dispatchAttachedToWindow()消息。最后调用了requestLayout()

  3. 然后是 recyclerView.setAdapter():

    1
    2
    3
    4
    5
    6
    7
    public void setAdapter(Adapter adapter) {
    // bail out if layout is frozen
    setLayoutFrozen(false);
    setAdapterInternal(adapter, false, true);
    processDataSetCompletelyChanged(false);
    requestLayout();
    }

    这里主要的流程当然是setAdapterInternal(adapter, false, true),不过还是先看看setLayoutFrozen()和requestLayout(),后面会反复用到里面的变量。

    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
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84

    //采用类似引用技术的方式来控制 requestLayout() 的调用,本身 requestLayout() 是有防止重复调用的机制的,这里为了提高效率减少刷新又增加了一层机制
    private int mInterceptRequestLayoutDepth = 0;

    // 如果 mInterceptRequestLayoutDepth == 0 并且 mLayoutFrozen是false,才真正requestLayout
    // 否则 仅仅是把标志位mLayoutWasDefered改成false
    @Override
    public void requestLayout() {
    if (mInterceptRequestLayoutDepth == 0 && !mLayoutFrozen) {
    super.requestLayout();
    } else {
    mLayoutWasDefered = true;
    }
    }

    void startInterceptRequestLayout() {
    mInterceptRequestLayoutDepth++;
    if (mInterceptRequestLayoutDepth == 1 && !mLayoutFrozen) {
    mLayoutWasDefered = false;
    }
    }

    void stopInterceptRequestLayout(boolean performLayoutChildren) {
    if (mInterceptRequestLayoutDepth < 1) {
    //noinspection PointlessBooleanExpression
    if (DEBUG) {
    throw new IllegalStateException("stopInterceptRequestLayout was called more "
    + "times than startInterceptRequestLayout."
    + exceptionLabel());
    }
    mInterceptRequestLayoutDepth = 1;
    }
    if (!performLayoutChildren && !mLayoutFrozen) {
    // Reset the layout request eaten counter.
    // This is necessary since eatRequest calls can be nested in which case the other
    // call will override the inner one.
    // for instance:
    // eat layout for process adapter updates
    // eat layout for dispatchLayout
    // a bunch of req layout calls arrive

    mLayoutWasDefered = false;
    }
    if (mInterceptRequestLayoutDepth == 1) {
    // when layout is frozen we should delay dispatchLayout()
    if (performLayoutChildren && mLayoutWasDefered && !mLayoutFrozen
    && mLayout != null && mAdapter != null) {
    dispatchLayout();
    }
    if (!mLayoutFrozen) {
    mLayoutWasDefered = false;
    }
    }
    mInterceptRequestLayoutDepth--;
    }

    /**
    * True if a call to requestLayout was intercepted and prevented from executing like normal and
    * we plan on continuing with normal execution later.
    * True表示一个requestLayout调用被拦截了,后面会在合适的时候执行 requestLayout
    */
    boolean mLayoutWasDefered;

    // setAdapter的时候会调用 setLayoutFrozen(false),其他地方暂时没看到。
    public void setLayoutFrozen(boolean frozen) {
    if (frozen != mLayoutFrozen) {
    assertNotInLayoutOrScroll("Do not setLayoutFrozen in layout or scroll");
    if (!frozen) {
    mLayoutFrozen = false;
    if (mLayoutWasDefered && mLayout != null && mAdapter != null) {
    requestLayout();
    }
    mLayoutWasDefered = false;
    } else {
    final long now = SystemClock.uptimeMillis();
    MotionEvent cancelEvent = MotionEvent.obtain(now, now,
    MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
    onTouchEvent(cancelEvent);
    mLayoutFrozen = true;
    mIgnoreMotionEventTillDown = true;
    stopScroll();
    }
    }
    }

    总结一下, 上面这几步基本就是初始化一些变量,然后requestLayout()更新界面。接下来则会进入onMeasure()流程,中间的具体过程可以参考 Android控件总结

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.isAutoMeasureEnabled()) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);

/**
* This specific call should be considered deprecated and replaced with
* {@link #defaultOnMeasure(int, int)}. It can't actually be replaced as it could
* break existing third party code but all documentation directs developers to not
* override {@link LayoutManager#onMeasure(int, int)} when
* {@link LayoutManager#isAutoMeasureEnabled()} returns true.
*/
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

final boolean measureSpecModeIsExactly =
widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
if (measureSpecModeIsExactly || 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 {
...
}

void defaultOnMeasure(int widthSpec, int heightSpec) {
// calling LayoutManager here is not pretty but that API is already public and it is better
// than creating another method since this is internal.
final int width = LayoutManager.chooseSize(widthSpec,
getPaddingLeft() + getPaddingRight(),
ViewCompat.getMinimumWidth(this));
final int height = LayoutManager.chooseSize(heightSpec,
getPaddingTop() + getPaddingBottom(),

ViewCompat.getMinimumHeight(this));

setMeasuredDimension(width, height);
}

public static int chooseSize(int spec, int desired, int min) {
final int mode = View.MeasureSpec.getMode(spec);
final int size = View.MeasureSpec.getSize(spec);
switch (mode) {
case View.MeasureSpec.EXACTLY:
return size;
case View.MeasureSpec.AT_MOST:
return Math.min(size, Math.max(desired, min));
case View.MeasureSpec.UNSPECIFIED:
default:
return Math.max(desired, min);
}
}
}

因为 LinearLayoutManager 的 mLayout.isAutoMeasureEnabled()方法直接返回了true,因此这里只看这个分支。这里面首先是调用mLayout.onMeasure()得到 defaultOnMeasure()拿到一个默认值。然后是一个判断跳过的条件,如果RecyclerView的长宽measure mode 都是 MeasureSpec.EXACTLY,那么这个控件的大小就是跟child无关的,可以直接跳过。否则的话直接在这里走 dispatchLayoutStep()的流程,而 dispatchLayoutStep2() 中会添加 childView,添加了之后再次重新 mLayout.setMeasuredDimensionFromChildren().最后判断是不是需要再次测量,再次进行测量的条件是 RecyclerView的长宽 measure mode 都不是 MeasureSpec.EXACTLY 并且子view的长宽也没有指定具体数值。

看一下 dispatchLayoutStep1 和 dispatchLayoutStep2

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126

/**
* 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
*/
private void dispatchLayoutStep1() {
mState.assertLayoutStep(State.STEP_START);
fillRemainingScrollValues(mState);
mState.mIsMeasuring = false;
startInterceptRequestLayout();
mViewInfoStore.clear();
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags(); // step 1 and step 2
saveFocusInfo();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);

if (mState.mRunSimpleAnimations) {
// Step 0: Find out where all non-removed items are, pre-layout
int count = mChildHelper.getChildCount();
for (int i = 0; i < count; ++i) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore() || (holder.isInvalid() && !mAdapter.hasStableIds())) {
continue;
}
final ItemHolderInfo animationInfo = mItemAnimator
.recordPreLayoutInformation(mState, holder,
ItemAnimator.buildAdapterChangeFlagsForAnimations(holder),
holder.getUnmodifiedPayloads());
mViewInfoStore.addToPreLayout(holder, animationInfo);
if (mState.mTrackOldChangeHolders && holder.isUpdated() && !holder.isRemoved()
&& !holder.shouldIgnore() && !holder.isInvalid()) {
long key = getChangedHolderKey(holder);
// This is NOT the only place where a ViewHolder is added to old change holders
// list. There is another case where:
// * A VH is currently hidden but not deleted
// * The hidden item is changed in the adapter
// * Layout manager decides to layout the item in the pre-Layout pass (step1)
// When this case is detected, RV will un-hide that view and add to the old
// change holders list.
mViewInfoStore.addToOldChangeHolders(key, holder);
}
}
}
if (mState.mRunPredictiveAnimations) {
// Step 1: run prelayout: This will use the old positions of items. The layout manager
// is expected to layout everything, even removed items (though not to add removed
// items back to the container). This gives the pre-layout position of APPEARING views
// which come into existence as part of the real layout.

// Save old positions so that LayoutManager can run its mapping logic.
saveOldPositions();
final boolean didStructureChange = mState.mStructureChanged;
mState.mStructureChanged = false;
// temporarily disable flag because we are asking for previous layout
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = didStructureChange;

for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
final View child = mChildHelper.getChildAt(i);
final ViewHolder viewHolder = getChildViewHolderInt(child);
if (viewHolder.shouldIgnore()) {
continue;
}
if (!mViewInfoStore.isInPreLayout(viewHolder)) {
int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
boolean wasHidden = viewHolder
.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
if (!wasHidden) {
flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
}
final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
if (wasHidden) {
recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
} else {
mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
}
}
}
// we don't process disappearing list because they may re-appear in post layout pass.
clearOldPositions();
} else {
clearOldPositions();
}
onExitLayoutOrScroll();
stopInterceptRequestLayout(false);
mState.mLayoutStep = State.STEP_LAYOUT;
}

private void processAdapterUpdatesAndSetAnimationFlags() {
if (mDataSetHasChangedAfterLayout) {
// Processing these items have no value since data set changed unexpectedly.
// Instead, we just reset it.
mAdapterHelper.reset();
if (mDispatchItemsChangedEvent) {
mLayout.onItemsChanged(this);
}
}
// simple animations are a subset of advanced animations (which will cause a
// pre-layout step)
// If layout supports predictive animations, pre-process to decide if we want to run them
if (predictiveItemAnimationsEnabled()) {
mAdapterHelper.preProcess();
} else {
mAdapterHelper.consumeUpdatesInOnePass();
}
boolean animationTypeSupported = mItemsAddedOrRemoved || mItemsChanged;
mState.mRunSimpleAnimations = mFirstLayoutComplete
&& mItemAnimator != null
&& (mDataSetHasChangedAfterLayout
|| animationTypeSupported
|| mLayout.mRequestedSimpleAnimations)
&& (!mDataSetHasChangedAfterLayout
|| mAdapter.hasStableIds());
mState.mRunPredictiveAnimations = mState.mRunSimpleAnimations
&& animationTypeSupported
&& !mDataSetHasChangedAfterLayout
&& predictiveItemAnimationsEnabled();
}

dispatchLayoutStep1(),第一次在onMeasure()里面执行的时候,因为这个时候 childview 还没有添加进来,很多逻辑都会跳过,此时会把step设置成 STEP_LAYOUT ,后面再次执行 onMeasure() 的时候, dispatchLayoutStep1() 主要做了两件事,一个是处理adapter数据的变化,这个主要是通过 mAdapterHelper.preProcess()来完成的,主要是把操作队列中 item 的 MOVE 操作移到最后,然后就是生成一个 animationInfo 数据并且把它保存在 mViewInfoStore 中。

dispatchLayoutStep2(),这里则是layoutChild实际执行的地方,会对child 执行 measure ,addview 和 layout 操作 ,出于灵活性的考虑,这里RecyclerView并没有实现,而是交给了 layout.onLayoutChildren() 方法去实现它,这样子就可以有不同的显示效果。需要注意的是这个方法可能会被调用多次,因此如果是自定义实现LayoutManager的话需要考虑到这种情况避免重复添加和layout。

在onMeasure()执行完之后,接下来会进入到 onLayout() :

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

protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}

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();
}

onLayout() 里面首先是 更新 mIsMeasuring 的状态为false,然后是根据 mState.mLayoutStep 来决定是不是要重新执行 dispatchLayoutStep1() 和 dispatchLayoutStep2() , 因为 mState.mLayoutStep 在 dispatchLayoutStep3() 中会被重置为STEP_START,所以这里其实是为了保障1和3有序执行。如果layout里面保存的宽高和 RecyclerView当前的宽高不一致(意思是大小变化了),则会重新进入dispatchLayoutStep2()再次执行 layout.onLayoutChildren() 方法。然后执行 dispatchLayoutStep3() ,最后更新 mFirstLayoutComplete 为true :

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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
 private void dispatchLayoutStep3() {
mState.assertLayoutStep(State.STEP_ANIMATIONS);
eatRequestLayout();
onEnterLayoutOrScroll();
mState.mLayoutStep = State.STEP_START;
if (mState.mRunSimpleAnimations) {
// Step 3: Find out where things are now, and process change animations.
// traverse list in reverse because we may call animateChange in the loop which may
// remove the target view holder.
for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore()) {
continue;
}
long key = getChangedHolderKey(holder);
final ItemHolderInfo animationInfo = mItemAnimator
.recordPostLayoutInformation(mState, holder);
ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
// run a change animation

// If an Item is CHANGED but the updated version is disappearing, it creates
// a conflicting case.
// Since a view that is marked as disappearing is likely to be going out of
// bounds, we run a change animation. Both views will be cleaned automatically
// once their animations finish.
// On the other hand, if it is the same view holder instance, we run a
// disappearing animation instead because we are not going to rebind the updated
// VH unless it is enforced by the layout manager.
final boolean oldDisappearing = mViewInfoStore.isDisappearing(
oldChangeViewHolder);
final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
if (oldDisappearing && oldChangeViewHolder == holder) {
// run disappear animation instead of change
mViewInfoStore.addToPostLayout(holder, animationInfo);
} else {
final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
oldChangeViewHolder);
// we add and remove so that any post info is merged.
mViewInfoStore.addToPostLayout(holder, animationInfo);
ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
if (preInfo == null) {
handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
} else {
animateChange(oldChangeViewHolder, holder, preInfo, postInfo,
oldDisappearing, newDisappearing);
}
}
} else {
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
}

// Step 4: Process view info lists and trigger animations
mViewInfoStore.process(mViewInfoProcessCallback);
}

mLayout.removeAndRecycleScrapInt(mRecycler);
mState.mPreviousLayoutItemCount = mState.mItemCount;
mDataSetHasChangedAfterLayout = false;
mState.mRunSimpleAnimations = false;

mState.mRunPredictiveAnimations = false;
mLayout.mRequestedSimpleAnimations = false;
if (mRecycler.mChangedScrap != null) {
mRecycler.mChangedScrap.clear();
}
if (mLayout.mPrefetchMaxObservedInInitialPrefetch) {
// Initial prefetch has expanded cache, so reset until next prefetch.
// This prevents initial prefetches from expanding the cache permanently.
mLayout.mPrefetchMaxCountObserved = 0;
mLayout.mPrefetchMaxObservedInInitialPrefetch = false;
mRecycler.updateViewCacheSize();
}

mLayout.onLayoutCompleted(mState);
onExitLayoutOrScroll();
resumeRequestLayout(false);
mViewInfoStore.clear();
if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
dispatchOnScrolled(0, 0);
}
recoverFocusFromState();
resetFocusInfo();
}

step3 首先是重置 layoutStep为 State.STEP_START,这样下次可以进入step1()了,然后是保存当前的 animationInfo 信息(此时已经经过step2添加了childview,信息会有变化),然后根据 状态信息的变化调用 animateChange() 触发动画的执行。之后是清理,重置相关的变量,为下一次 dispathLayout() 做准备。

最后是 onDraw() 方法,不过搜索 onDraw() 方法的时候却发现 RecyclerView 还重写了 draw() 方法,回顾一下控件的整个一般性流程

performTraversals

结合 RecyclerView 中的代码:

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
@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);
}

...

// If some views are animating, ItemDecorators are likely to move/change with them.
// Invalidate RecyclerView to re-draw decorators. This is still efficient because children's
// display lists are not invalidated.
if (!needsInvalidate && mItemAnimator != null && mItemDecorations.size() > 0
&& mItemAnimator.isRunning()) {
needsInvalidate = true;
}

if (needsInvalidate) {
ViewCompat.postInvalidateOnAnimation(this);
}
}

@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(c) ,先调用 super.draw(c) 开始走 onDraw() 流程 , 然后 dispatchDraw() 分发给子view 去绘制自己, 最后再接着调用 onDrawOver() 进行回调。绘制的流程中主要是 ItemDecoration 的绘制,这个在下一篇说。