Recycleview的绘制过程

掉用Recycleview的setAdapter的时候会去执行recycleview的requestlayout:

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

然后在(Recycleview)onLayout里面执行了dispatchLayout:

1
2
3
4
5
6
7
8
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
// 执行了dispatchLayout
dispatchLayout();
TraceCompat.endSection();
mFirstLayoutComplete = true;
}

接着调用dispatchLayout(),这里面可以看到很关键的三个方法:dispatchLayoutStep1() dispatchLayoutStep2() dispatchLayout3()。 最主要是在dispatchLayoutStep2()里面,有一个while循环,在whill循环里面我们通过判断当前itempostion的值是否小于我们在adapter里面设置的getItemCount()的值,来动态addView子view进去。现在我们来看下dispatchLayoutStep2()方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
void dispatchLayout() {
if (mAdapter == null) {
// leave the state in START
return;
}
if (mLayout == null) {
return;
}
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();// 里面有一个while循环,通过给定的adapter的getItemCount值,判断添加几个进去。
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}

来看下dispatchLayout2()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private void dispatchLayoutStep2() {
----
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

// Step 2: Run layout
mState.mInPreLayout = false;
// 进行循环遍历addVie
mLayout.onLayoutChildren(mRecycler, mState);

mState.mStructureChanged = false;
mPendingSavedState = null;
------
}

viewpager2每次调用setCurrentItem(position)的时候会触发requestlayout,requestlayout都是层层往上回归调用的,知道调用到viewRoomtImpl的requestlayout方法,然后调用到viewrootimpl的perfromTraversals方法。他又是由viewRootImpl的doTraversal调用。doTraversal是在一个runable里面调用的。

Recycleview的suppressLayout里面将mLayoutSuppressed

  • mLayoutSuppressed:是否阻止recycleview的layout

  • mLayoutWasDefered:如果对 requestLayout 的调用被拦截并阻止正常执行,并且我们计划稍后继续正常执行,则为 True。

  • mInterceptRequestLayoutDepth:当前调用startInterceptRequestLayout()的嵌套深度(调用startInterceptRequestLayout()的次数-调用stopInterceptRequestLayout的次数(boolean))。这用于指示我们是否应该推迟由RecyclerView子节点的布局请求引起的布局操作。

Recycleview里面有一个教 mState:State 的变量,用来保存当前recycleliew当前所处于的状态,分别为:

  • STEP_STARP= 1
  • STEP_LAYOUT = 1<<1
  • STEP_ANIMATIONS = 1

默认情况下mState.mLayoutStep的初始值为STEP_START。

mState.mLayoutStep的值有三个地方进行赋值,分别为:

  • Recycleview的dispatchLayoutStep1() 将其赋值为State.STEP_Layout
  • Recycleview的dispatchLayoutStep2() 将其赋值为State.Step.STEP_ANIMATIONS
  • Recycleview的dispatchLayout3() 将其赋值为State.STEP_START

来看下Recycleview的onMeasure方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
protected void onMeasure(int widthSpec,int heightSpec){
if(mLayout.isAutoMeasureEnabled()){
if(mState.mLayoutStep == Step.STEP_START){
dispatchLayoutStep1();
}
mState.mIsMeaturing = true;
dispatchLayoutStep2();
if(mLayout.shouldMeasureTwice()){
mState.mIsMeasuring = true;
dispatchLayoutStep2();
}
}else{

}
startInterceptRequestLayout(mRecycler,mState,widthSpec,heightSpec);
stopInterceptRequestLayout(false);
mState.mInPreLayout = false;
}

来看下onLayout方法:

1
2
3
4
protected void onLayout(boolean changed,int l,int t,int r,int b){
dispatchLayout();
mFirstLayoutComplete = true;
}
1
2
3
4
5
6
7
8
9
Recycler{
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);
}

在onResume里面调用到Recycleview的onAttachToWindow方法,将参数mIsAttaches设置为了true。

然后Recycleview在onAttachToWindow里面里面调用了mLayout.dispatchAttachedToWindow()。

RecyckeView的布局分为三个步骤

  • dispatchLayoutStep1
  • dispatchLayoutStep2
  • dispatchLayoutStep3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void dispatchLayout() {
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
// 解释在下面
dispatchLayoutStep1();
mLayout.setExactMeasureSpecsFrom(this);
//
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
|| mLayout.getHeight() != getHeight()) {
mLayout.setExactMeasureSpecsFrom(this);
dispatchLayoutStep2();
} else {
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}
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
private void dispatchLayoutStep1() {
// mLayoutStep的默认值是State.STEP_START
// 判断当前状态mLayoutStep和设置进去的State值是否相等,如果不等就抛出异常,你在不对的状态去干了某个状态的事情
// 比如在dispatchLayoutStep1()这个方法结束后里将mLayoutStep的值设置为State.STEP_LAYOUT
mState.assertLayoutStep(State.STEP_START);
// 判断当前的滑动状况,如果还在滑动中那么将会设置state.mRemainingScrollHorizontal=还需滑动的距离
// 如果滑动状况是SCROLL_STATE_IDLE空闲状态,那么剩余滑动距离就设置为0
fillRemainingScrollValues(mState);
// 当前recycleview设置为不是正在测量状态
mState.mIsMeasuring = false;
// 开启分发布局 将mInterceptRequestLayoutDepth++
// 然后判断mInterceptRequestLayoutDept == 1 && !mLayoutSuppressed
//Suppressed(布局镇压) 如果没有被镇压那么就会设置 mLayoutWasDefered = false
// Defered(延期)
startInterceptRequestLayout();
mViewInfoStore.clear();
// 就干了一件事情mLayoutOrScrollCounter++
// 布局或者滚动的计数器。他会在布局和滚动的时候才会增加
onEnterLayoutOrScroll();
processAdapterUpdatesAndSetAnimationFlags();
saveFocusInfo();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
mState.mInPreLayout = mState.mRunPredictiveAnimations;
mState.mItemCount = mAdapter.getItemCount();
//找到最小最大子布局位置
findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);

// mRunSimpleAnimations的值默认为false
if (mState.mRunSimpleAnimations) {
}
// mRunPredictiveAnimations默认为false
if (mState.mRunPredictiveAnimations) {
} else {
clearOldPositions();
}
// 将mLayoutOrScrollCounter-- 计数器减
onExitLayoutOrScroll();
// 将mLayoutWasDefered = fasle
// mInterceptRequestLayoutDepth--
stopInterceptRequestLayout(false);
// 将当前布局状态设置为 State.STEP_LAYOUT
mState.mLayoutStep = State.STEP_LAYOUT;
}
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
private void dispatchLayoutStep2() {
// 开启分发布局 将mInterceptRequestLayoutDepth++
// 然后判断mInterceptRequestLayoutDept == 1 && !mLayoutSuppressed
// Suppressed(布局镇压) 如果没有被镇压那么就会设置 mLayoutWasDefered = false
// Defered(延期)
startInterceptRequestLayout();
// 就干了一件事情mLayoutOrScrollCounter++
// 布局或者滚动的计数器。他会在布局和滚动的时候才会增加
onEnterLayoutOrScroll();
// 判断当前状态对不对
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
// 跳过预处理,一次性应用所有更新。
mAdapterHelper.consumeUpdatesInOnePass();
mState.mItemCount = mAdapter.getItemCount();
//删除了PreviousLayout后的不可见项目计数
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;

// Step 2: Run layout
// 设置状态是不是准备在layout = false
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);

mState.mStructureChanged = false;
mPendingSavedState = null;

mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
// 下一步状态设置为动画
mState.mLayoutStep = State.STEP_ANIMATIONS;
// 将mLayoutOrScrollCounter-- 计数器减
onExitLayoutOrScroll();
// 将mLayoutWasDefered = fasle
// mInterceptRequestLayoutDepth--
stopInterceptRequestLayout(false);
}
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
private void dispatchLayoutStep3() {
mState.assertLayoutStep(State.STEP_ANIMATIONS);
startInterceptRequestLayout();
onEnterLayoutOrScroll();
// 设置下一步的布局状态为START
mState.mLayoutStep = State.STEP_START;
// 默认为false
if (mState.mRunSimpleAnimations) {
}

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

mState.mRunPredictiveAnimations = false;
mLayout.mRequestedSimpleAnimations = false;
if (mRecycler.mChangedScrap != null) {
mRecycler.mChangedScrap.clear();
}
if (mLayout.mPrefetchMaxObservedInInitialPrefetch) {
mLayout.mPrefetchMaxCountObserved = 0;
mLayout.mPrefetchMaxObservedInInitialPrefetch = false;
mRecycler.updateViewCacheSize();
}

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

分析完成可以看到Recycleview的页面展示是在disPatchLayoutStep2()里面的mLayout.onLayoutChildren(mRecycler,mState)。

看下LinearLayoutManager:

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
//LinearLayoutManager.java
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
//1)通过检查子节点和其他变量,找到一个锚点坐标和一个锚点项目位置。
//2)向开始填充,从底部堆叠
//3)向结束填充,从顶部堆叠
//4)滚动来满足像从底部堆叠这样的要求。创建布局状态

//fill的返回值是这次填充recycleviewh
if (mPendingSavedState != null || mPendingScrollPosition != RecyclerView.NO_POSITION) {
if (state.getItemCount() == 0) {
removeAndRecycleAllViews(recycler);
return;
}
}
if (mPendingSavedState != null && mPendingSavedState.hasValidAnchor()) {
mPendingScrollPosition = mPendingSavedState.mAnchorPosition;
}
// 创建对象LayoutState
ensureLayoutState();
// 某些情况下,我们可能不想回收子布局(例如布局)
mLayoutState.mRecycle = false;
// 如果是垂直布局:mShouldReverseLayout = false
// 否则 mShouldReverseLayout = true
resolveShouldLayoutReverse();
// 获取需要焦点的孩子view
final View focused = getFocusedChild();
// 第一次布局的时候这里进不去 mPendingScrollPosition默认值就是NO_POSITION
if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
|| mPendingSavedState != null) {
mAnchorInfo.reset();
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
// 第一次布局的时候focused是null
} else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
>= mOrientationHelper.getEndAfterPadding()
|| mOrientationHelper.getDecoratedEnd(focused)
<= mOrientationHelper.getStartAfterPadding())) {
mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
}
// 判断是上滑动还是下滑动
mLayoutState.mLayoutDirection = mLayoutState.mLastScrollDelta >= 0
? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
// 计算剩余需要滑动的距离
calculateExtraLayoutSpace(state, mReusableIntPair);
int extraForStart = Math.max(0, mReusableIntPair[0])
+ mOrientationHelper.getStartAfterPadding();
int extraForEnd = Math.max(0, mReusableIntPair[1])
+ mOrientationHelper.getEndPadding();
if (state.isPreLayout() && mPendingScrollPosition != RecyclerView.NO_POSITION
&& mPendingScrollPositionOffset != INVALID_OFFSET) {
final View existing = findViewByPosition(mPendingScrollPosition);
if (existing != null) {
final int current;
final int upcomingOffset;
if (mShouldReverseLayout) {
current = mOrientationHelper.getEndAfterPadding()
- mOrientationHelper.getDecoratedEnd(existing);
upcomingOffset = current - mPendingScrollPositionOffset;
} else {
current = mOrientationHelper.getDecoratedStart(existing)
- mOrientationHelper.getStartAfterPadding();
upcomingOffset = mPendingScrollPositionOffset - current;
}
if (upcomingOffset > 0) {
extraForStart += upcomingOffset;
} else {
extraForEnd -= upcomingOffset;
}
}
}
int startOffset;
int endOffset;
final int firstLayoutDirection;
if (mAnchorInfo.mLayoutFromEnd) {
firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
: LayoutState.ITEM_DIRECTION_HEAD;
} else {
firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
: LayoutState.ITEM_DIRECTION_TAIL;
}

onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
detachAndScrapAttachedViews(recycler);
mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mIsPreLayout = state.isPreLayout();
// noRecycleSpace not needed: recycling doesn't happen in below's fill
// invocations because mScrollingOffset is set to SCROLLING_OFFSET_NaN
mLayoutState.mNoRecycleSpace = 0;
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
final int firstElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForEnd += mLayoutState.mAvailable;
}
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForEnd;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;

if (mLayoutState.mAvailable > 0) {
// end could not consume all. add more items towards start
extraForStart = mLayoutState.mAvailable;
updateLayoutStateToFillStart(firstElement, startOffset);
mLayoutState.mExtraFillSpace = extraForStart;
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;
}
} else {
updateLayoutStateToFillEnd(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForEnd;
// 从我们AnchorInfo.mPosition位置往下布局
fill(recycler, mLayoutState, state, false);
// 布局已知的item所消耗的距离,高度。
endOffset = mLayoutState.mOffset;
final int lastElement = mLayoutState.mCurrentPosition;
if (mLayoutState.mAvailable > 0) {
extraForStart += mLayoutState.mAvailable;
}
updateLayoutStateToFillStart(mAnchorInfo);
mLayoutState.mExtraFillSpace = extraForStart;
mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
//从我们AnchorInfo.mPosition位置往上布局
fill(recycler, mLayoutState, state, false);
startOffset = mLayoutState.mOffset;

if (mLayoutState.mAvailable > 0) {
extraForEnd = mLayoutState.mAvailable;
updateLayoutStateToFillEnd(lastElement, endOffset);
mLayoutState.mExtraFillSpace = extraForEnd;
fill(recycler, mLayoutState, state, false);
endOffset = mLayoutState.mOffset;
}
}

if (getChildCount() > 0) {

if (mShouldReverseLayout ^ mStackFromEnd) {
int fixOffset = fixLayoutEndGap(endOffset, recycler, state, true);
startOffset += fixOffset;
endOffset += fixOffset;
fixOffset = fixLayoutStartGap(startOffset, recycler, state, false);
startOffset += fixOffset;
endOffset += fixOffset;
} else {
int fixOffset = fixLayoutStartGap(startOffset, recycler, state, true);
startOffset += fixOffset;
endOffset += fixOffset;
fixOffset = fixLayoutEndGap(endOffset, recycler, state, false);
startOffset += fixOffset;
endOffset += fixOffset;
}
}
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
if (!state.isPreLayout()) {
mOrientationHelper.onLayoutComplete();
} else {
mAnchorInfo.reset();
}
mLastStackFromEnd = mStackFromEnd;
if (DEBUG) {
validateChildOrder();
}
}

detachAndScrapAttachedViews

通过直接的翻译:移除和添加view。

      在代码里面我们发现recycleview使用了viewGrouo.detachViewFromParent()和attachViewToParent()。为什么要用这两个方法,因为这比起addView和removeView来说是轻量级的。它不会触发requestLayout方法。

      执行detachAndScrapAttachedViews方法,他将recycleview所有的view都执行了detach操作,从recycleview里面移除。

      如果这个viewHolder含有移除标志位 INVALID(无效) 或者 不含更新 或者 可以重用更新的

      符合以上条件:这个viewHolder将会被添加到mAttachedScrap这个数组里面。否则添加到mChangedScrap数组里面。

  • 报废或回收视图
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
1
mChildHelper = ChildHelper
1
mAdapterHelper = AdapterHelper

在LinearLayoutManager的构造函数里面:

1
2
3
4
5
6
7
8
9
10
public LinearLayoutManager(Context context, AttributeSet attrs, int defStyleAttr,
int defStyleRes) {
Properties properties = getProperties(context, attrs, defStyleAttr, defStyleRes);
// 初始化我们的方向计算辅助类 orientationHelper类
setOrientation(properties.orientation);
// 设置我们布局方向是反向还是正向 mReverseLayout = true or false
setReverseLayout(properties.reverseLayout);
// 设置填充方向 当 stack from bottom 设置为 true 时,列表从视图底部开始填充其内容 // mStackFromEnd = true or false
setStackFromEnd(properties.stackFromEnd);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public void setOrientation(@RecyclerView.Orientation int orientation) {
assertNotInLayoutOrScroll(null);
// mOrientation的默认值是vertical
// mOrientationHelper 由于有很多关于方向上的计算需要处理,然后为了保持代码的易读性,就将计算的操作交给了OrientationHelper这个类做了。
// 给mAnchorInfor设置上我们的方向计算器orientationHelper。
if (orientation != mOrientation || mOrientationHelper == null) {
mOrientationHelper =//创建了两个一个是垂直的方向计算 一个是水平创建
OrientationHelper.createOrientationHelper(this, orientation);
mAnchorInfo.mOrientationHelper = mOrientationHelper;
mOrientation = orientation;
requestLayout();
}
}

页面上滑的时候滑动增量是大于0的,页面下滑的时候滑动增量式小于零:

1
2
3
//通过最后一刻的滑动增量 判断滑动方向,并且设置到LayoutState里面。
mLayoutState.mLayoutDirection = mLayoutState.mLastScrollDelta >= 0
? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;

calculateExtraLayoutSpace

这个方法很重要,他是做缓存计算的,比如,如果你在向上滑动的时候,你想预加载下一项或者夏两项的时候,我们就可以通过这个方法,计算缓存的临界值。在LinearLayoutManager里面,他的计算会报错到extreLayoutSpace数组里面,extraLayoutSpace[0] = 顶部/左侧的额外空间。extraLayoutSpace[1]底部/右侧的额外空间。默认情况下,LinearlayoutManager在平滑滚动时会在滚动方向上额外不知一个项目,并在所有其他i情况下都不会布置额外的空间。你可以覆盖这个方法以实现自己的自定义预缓存逻辑。使用Recycleview.State.hasTargerScrollPosition()找出是否正在平滑滚动到某项位置,并使用Recycleview.Satte.getTargetScrollPosition()找出它正在滚动到哪个项目。

img-0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* 通过源码可以看到在LinearlayoutManager。缓存值是recycleview的高度。
*/
protected void calculateExtraLayoutSpace(@NonNull RecyclerView.State state,
@NonNull int[] extraLayoutSpace) {
int extraLayoutSpaceStart = 0;
int extraLayoutSpaceEnd = 0;

// 看源码知道这个extraScrollSpace的值是recycleview的高度-顶部边距减去底部边距剩下的空间
int extraScrollSpace = getExtraLayoutSpace(state);
// mLayoutDirection在上面我们已经分析了。在onLayoutChildren方法里面
// 会根据recycleview的每次滑动值也就是scrollby的值。判断是在向上滑动还是在向
// 下滑动。
if (mLayoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
// 如果是向上滑动 就设置上的值 如果下就设置下的值
extraLayoutSpaceStart = extraScrollSpace;
} else {
extraLayoutSpaceEnd = extraScrollSpace;
}

extraLayoutSpace[0] = extraLayoutSpaceStart;
extraLayoutSpace[1] = extraLayoutSpaceEnd;
}

scrapOrRecycleView

  • 报废或回收视图

就是做了个情况操作,把展示在recycleview的内容全部干掉

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
return;
}
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
//如果这个viewHolder不可用了 && 这个viewHolder没有被移除,那么就把这个view从
//recycleview里面移除调
removeViewAt(index);
//内部实现检查视图是否被废弃或附加,如果是则抛出异常。 公共版本在调用回收之前取消报废
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}

Recycler

1
2
3
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
RecycledViewPool mRecyclerPool;

这些数组都找不到对应的viewHolder,那么就会调用createViewHolder方法。

ChildHelper

1
final List<View> mHiddenViews;

AdapterHelper

1
final ArrayList<UpdateOp> mPostponedList = new ArrayList<UpdateOp>();

mPendingScrollPosition:目标要滚动到的位置

mAnchorInfo.mLayoutFromEnd:是从上往下还是从下往上。

在onLayoutChildren里面判断有没有目标滚动位置(mPendingScrollPosotion)。对AnchorInfo的数据进行了重置,然后设置了mAnchorInfo.mLayoutFromEnd的值(是从上往下还是从下往上布局)。然后执行了updateAnchorInfoForLayout方法目的是计算锥的位置和坐标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// LinearLayoutManager
private void updateAnchorInfoForLayout(RecyclerView.Recycler recycler, RecyclerView.State state,
AnchorInfo anchorInfo) {
if (updateAnchorFromPendingData(state, anchorInfo)) {
return;
}

if (updateAnchorFromChildren(recycler, state, anchorInfo)) {
return;
}
if (DEBUG) {
Log.d(TAG, "deciding anchor info for fresh state");
}
// 设置值:AnchorInfo.mCoordinate = 如果是从尾开始布局那么mCoordinate的值 = 整个recycleviewde高度 如果是从头开始布局那么mCoordinate的值 = 就等于recyclew的paddingtop值。
anchorInfo.assignCoordinateFromPadding();
// 设置当前布局的位置 anchorInfo的当前位置mPosition = 0
anchorInfo.mPosition = mStackFromEnd ? state.getItemCount() - 1 : 0;
}

AnchorInfo

  • mPosition:用户设置的最先起始布置的位置。默认为0。如果你想从中间开始布局,可以设置你的数据/2的那个位置。然后布局的时候他会从这个位置先往下布局布局结束后在往上

  • mCoordinate:我在onLayoutChildren的updateAnchorInfoForLayout看到了他的首次赋值是recycleview的paddingTop

  • mValid:默认为false。有效的意思。

  • mLayoutFromEnd:是从上往下还是从下往上布局。

LayoutState

在onLayoutChildren里面进行了对象创建。

  • mLayoutDirection: 根据每次的滑动值来判断滑动方向。如果mLastScrollDelta>=0 则等于1 否则等于-1
  • mLastScrollDelta:滑动的改变值。
  • mInfinite:只有当recycleview的测量模式为UNSPECIFIED 并且 recycleview的高度为0,这个值才为true。他的中文意思是 无限。
  • mIsPreLayout: 他的值等于 state.isPreLayout
  • mRecycle = faslse
  • mNoRecycleSapce = 0
  • mAvailable:recycleview的高度 - 偏移量(初始的时候偏移量为0)。在滑动的时候 这个值等于 滑动的距离 - 最后一个视图全部展现出来需要滑动的距离。官方解释:在布局放向上,我们应该填充的像素数。
  • mScrollingOffset:默认值 = LayoutState.SCROLLING_OFFSET_NaN。这个值记录了最后一个子view全部展现在屏幕 或者最上面一个子view全部展现在屏幕上所需要滑动的距离。官方解释:在滚动的时候,这个值代表在不创建新的视图的情况下可以进行的滚动量。
  • mItemDirection:布局的方向,如果不是反向布局这个值等于1
  • mCurrentPosition:当前的位置他的首次赋值等于anchorInfo.mPosition
  • mLayoutDirection:等于1
  • mExtraFillSpace:中文翻译是额外填充空间。
  • mScrapList
  • mOffset:保存布局完成所有view所需要消耗的高度。根据方向进行的累加。每布局完成一次叠加一次。

这张图是对mAvailable 和 mScrollingOffset参数含义的演示。

img-0

LayoutChunkResult

  • mCinsumed:布局一个item所需要消耗的高度
  • mFinished:是否结束布局
  • mIgnoreConsumed:是否忽略这次布局的消耗值
  • mFocusable:是否获取焦点

State

  • mIsMeasuring: 表示是否正在测量 在onLayout的时候设置为了fasle
  • mLayoutStep:表示下一步所处阶段,根据这个变量来选择recycleview布局得三步走走哪个。
  • mRemainingScrollHorizontal: 在横向还需要滑动的距离。
  • mRemainingScrollVertical: 在纵向还需要滑动的距离。
  • mItemCount: 它的值等于我们给adapter设置的值。

fill

LinearLayoutManager

哇 这个fill方法只是进行了addView操作,正真的页面平移是在fill结束后才进行的。我们可以看scrollBy方法:先执行了fill方法。然后在执行了offsetChildren方法。

1
2
3
4
5
6
7
8
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
// fill方法只是进行的addView操作.
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
// 这个offsetChildren方法才是真正的让页面进行了平移
mOrientationHelper.offsetChildren(-scrolled);
return scrolled;
}

正真进行视图的添加的方法:

里面有一个循环,当剩下的布局空间 > 0 那么就继续这个循环,往Recycleview里面添加布局view。每次添加完成一个view,remainSpace的值就会减去布局这个view所消耗的高度。叠减知道减到小于0。并且我们也没有更过的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
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
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
// 这个值等于 recycleview的高度 - 偏移量(初始的时候偏移量为0)
// 在触发页面滚动的时候,这个layoutState.mAvaliable的值等于
// 等于: 这次页面的滑动距离 - 最后一个子view划出屏幕需要的距离 具体代码可以看
// updateLayoutState()
final int start = layoutState.mAvailable;
if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
// 在触发滑动事件的时候,mScrollingOffset的值会被赋值为 最后一个子view全部展现在屏幕上所需要滑动的距离。所以在滑动的时候这个判断会成立,就进来了。
if (layoutState.mAvailable < 0) {
// 小于0代表 这次触发的滑动的距离值 小于 最后一个子视图全部展示出来所需要的值
// 这里减一下 mScrollingOffset = 这次滑动的触发的滑动距离
layoutState.mScrollingOffset += layoutState.mAvailable;
}
// 这个方法可搞死我了。我放在下面详细讲解这个方法。 这个方法完成了对不可见视图的回收操作。
recycleByLayoutState(recycler, layoutState);
}
// remainingSpace 剩余空间 1. 在触发滑动的时候 mAcailable的值 = 滑动距离 - 最后一个view全部划出屏幕需要的距离 如果滑动距离小于最后一个view全部出现距离,则不需要添加新的view进来,也就不会执行这个while循环里面的代码。也就不会执行addView代码。
// 2. 在第一次布局的时候 mAvailable 的值等于 recycleview的高度
// 这个remaingSpace起到了一个是否要执行addView的操作。1. 在初始化布局的时候,mAvailable的值是recycleview的高度,第一次布局页面是空的需要我们进行布局。
// 2. 在滚动的时候,如果滚动的距离 小于最后一个view全部展示出来的view 则说明这次滚动不足以出现一个新的视图,那么我们就没必要执行addView操作。只有当滑动的距离大于了最后一个view全部展示出来的距离的时候才会触发addView操作。
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
//mInfinite只有在recycleview的高度为0并且没有指定测量模式才会为true
//remainingSpace:当偏移量超过了recycleview的高度的时候这个值会变成false
// hasMore:当当前的mCurrentPosition大于adapter里面设置的值是 等于 fasle
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
// 重置layoutChunkResult的状态
// mConsumed=0.mFinished=fasle.mIgnoreConsumed=false.mFocusable=fasle
layoutChunkResult.resetInternal();
// 进行布局
layoutChunk(recycler, state, layoutState, layoutChunkResult);
// 当我们找不到一个可用的view的时候,这个view就等于了null 这个时候mFinished的值就是true
if (layoutChunkResult.mFinished) {
break;
}
// layoutChunkResult.mConsumed值=布局一个item所需要消耗的高度
// 累加所有view布局完成后消耗的值
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
*/
if (!layoutChunkResult.mIgnoreConsumed || layoutState.mScrapList != null
|| !state.isPreLayout()) {
// 还剩下的可以布局的空间
layoutState.mAvailable -= layoutChunkResult.mConsumed;
// 还剩下可以布局的空间
remainingSpace -= layoutChunkResult.mConsumed;
}

if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) {
layoutState.mScrollingOffset += layoutChunkResult.mConsumed;
if (layoutState.mAvailable < 0) {
layoutState.mScrollingOffset += layoutState.mAvailable;
}
recycleByLayoutState(recycler, layoutState);
}
if (stopOnFocusable && layoutChunkResult.mFocusable) {
break;
}
}
return start - layoutState.mAvailable;
}

void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
// 从recycler里面获取我们需要的view
// 分别从recycler的四级缓存里面拿,如果都找不到那么就去执行view的创建。执行我们熟知的onCreateViewHolder()
View view = layoutState.next(recycler);
if (view == null) {
// 在recycler里面也找不到,创建view也失败了
result.mFinished = true;
return;
}
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
addView(view);
} else {
addDisappearingView(view);
}
measureChildWithMargins(view, 0, 0);
// mOrientationHelper.getDecoratedMeasurement(view); 获取的值是一个itemView所需要使用到的高度。marginTop bottom 分界线的高度都包括在里面。
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
int left, top, right, bottom;
if (mOrientation == VERTICAL) {
if (isLayoutRTL()) {
right = getWidth() - getPaddingRight();
left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
} else {
left = getPaddingLeft();
right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
bottom = layoutState.mOffset;
top = layoutState.mOffset - result.mConsumed;
} else {
top = layoutState.mOffset;
bottom = layoutState.mOffset + result.mConsumed;
}
} else {
top = getPaddingTop();
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);

if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
right = layoutState.mOffset;
left = layoutState.mOffset - result.mConsumed;
} else {
left = layoutState.mOffset;
right = layoutState.mOffset + result.mConsumed;
}
}
// 布局view
layoutDecoratedWithMargins(view, left, top, right, bottom);
if (params.isItemRemoved() || params.isItemChanged()) {
result.mIgnoreConsumed = true;
}
// 这个view是否有焦点
result.mFocusable = view.hasFocusable();
}
img-0

Recycleview是怎么创建viewHolder的

这里需要看LinearLayoutManager的layoutChunk方法里的layoutState.next方法。

在next方法里面调用了recycler.getViewForPosition(position)方法获取想要的view。

来看下recycler.getViewForPosition

recycler.getViewForPosition

首先进入getScrapOrHiddenOrCachedHolderForPostion方法:

遍历数组:mAttachedScrap : 屏幕内

遍历数组:Childhelper.mHiddenViews

遍历数组:mCachedViews : 屏幕外 大小为2

遍历数组:mViewCacheExtension : 自定义缓存

遍历数组:mRecyclerPool : 缓存池

在没有的话:执行createViewHolder

updateLayoutState(int layoutDirection,int requiredSpace,booldean canUseExistingSpace,Recycleview.State state)

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
// 用来保存最后一个view全部展示出来需要滑动的距离
// 或者是最上面一个view全部展示出来需要的距离。
// 以及这次滑动值requireSpace - 最后一个view全部展示出来需要滑动的距离 他们的值的状态
// 如果大于0代表这次滑动最后一个view可以全部展示出来,需要去添加一个新view进来了。
private void updateLayoutState(int layoutDirection, int requiredSpace,
boolean canUseExistingSpace, RecyclerView.State state) {
// 这里假设手指运动方向是从下往上 则 layoutDirection = LayoutState.LAYOUT_END
mLayoutState.mInfinite = resolveIsInfinite(); //fasle
mLayoutState.mLayoutDirection = layoutDirection; // 1
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
calculateExtraLayoutSpace(state, mReusableIntPair);// 现在看来啥也没干
int extraForStart = Math.max(0, mReusableIntPair[0]);
int extraForEnd = Math.max(0, mReusableIntPair[1]);
boolean layoutToEnd = layoutDirection == LayoutState.LAYOUT_END;
mLayoutState.mExtraFillSpace = layoutToEnd ? extraForEnd : extraForStart;
mLayoutState.mNoRecycleSpace = layoutToEnd ? extraForStart : extraForEnd;
int scrollingOffset;
if (layoutToEnd) {//这里为true
// 我没搞懂这里为啥要叠加recycleview的paddingBottom的值
mLayoutState.mExtraFillSpace += mOrientationHelper.getEndPadding();
// 找到最底下的view
final View child = getChildClosestToEnd();
// 由于不是反向布局 这里的mItemDirection = 1
mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD
: LayoutState.ITEM_DIRECTION_TAIL;
// 比如我们页面已经添加了12个子view,那么这个getPosition(child)的值就是11 然后 由于是下上滑动的那么mItemDirection的值就是1 结果显而易见 mLayoutState.mCurrentPositiond = 11 + 1
mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
// mOffset 的值 等于 当前这个view距离父视图底部的距离。bottom的值。
mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
// calculate how much we can scroll without adding new children (independent of layout)
// mOrientationHelper.getDecoratedEnd(child) 由于这个child是最底部的这个子视图。所以这个获取的值就是最底下的view距离recycleview顶部的距离。
//mOrientationHelper.getEndAfterPadding()获取的是recycleview的高度-paddingBottom值
// 这两个一减代表最后一个字view全部出现到屏幕上需要移动的距离
scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
- mOrientationHelper.getEndAfterPadding();

} else {
final View child = getChildClosestToStart();
mLayoutState.mExtraFillSpace += mOrientationHelper.getStartAfterPadding();
mLayoutState.mItemDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL
: LayoutState.ITEM_DIRECTION_HEAD;
mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child);
// 最上面一个view全部出现到屏幕上需要移动的距离
scrollingOffset = -mOrientationHelper.getDecoratedStart(child)
+ mOrientationHelper.getStartAfterPadding();
}
// requiredSpace的值就是滚动的值
mLayoutState.mAvailable = requiredSpace;
// 这个canUseExistingSpace的值等于true
if (canUseExistingSpace) {
// scrollingOffset代表最后一个view全部划出屏幕的距离
// mLayoutState.mAvailable 代表此次滑动需要的距离
// 两者一减: 如果结果>0 代表最后一个view已经全部出现在屏幕了,这个时候需要去添加一个新的view
// 如果减的<0,则代表最后一个视图还不足以全部出现,也就不用添加新的页面了
mLayoutState.mAvailable -= scrollingOffset;
}
// 设置为最后一个view全部展示出来需要的距离
mLayoutState.mScrollingOffset = scrollingOffset;
}

      再触发一次滑动的时候,比如这次滑动距离为 10 。 最后一个view全部展示出来需要的距离为20。那么:mAvailable = 10 - 20 = -10 mScrollingOffset = 20。

      然后进入fill方法。当mAvailable<0 那么重新给mScrollingOffset 设值为: 20 - 10 = 10

scrollBy

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
// LinearLayoutManager
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
if (getChildCount() == 0 || delta == 0) {
return 0;
}
ensureLayoutState();
mLayoutState.mRecycle = true;
final int layoutDirection = delta > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDelta = Math.abs(delta);
// 这个方法上面讲了是干嘛的。
updateLayoutState(layoutDirection, absDelta, true, state);
// mLayoutState.mScrollingOffset 表示最后一个子view全部展现在屏幕上所需要的距离
// 或者是最顶上的view全部展现在屏幕上需要的距离
// 现在需要看下这个fill干了啥
// 如果这次滑动的距离不足以让最后一个view展示出来的话,那么fill的返回值就为0
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
if (consumed < 0) {
// 表示没消耗滑动距离
return 0;
}
final int scrolled = absDelta > consumed ? layoutDirection * consumed : delta;
// 这里会回调到recycleview里面执行offsetChildrenVertical方法。遍历全部的view执行offsetTopAndBottom方法。实现他们的位移变化。
// 既然这里是做了位移变化,那上面的fill方法是在干嘛?
mOrientationHelper.offsetChildren(-scrolled);
mLayoutState.mLastScrollDelta = scrolled;
return scrolled;
}

recycleByLayoutState

LinearLayoutManager

这个方法完成了对不可见视图的回收操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
// 触发滑动的时候,mRecycle设置为了true
if (!layoutState.mRecycle || layoutState.mInfinite) {
return;
}
// 在方法fill里面。执行了layoutState.mScrollingOffset += layoutState.mAvailable;
// 由于mAvailable 的值 = 滑动距离 - 最后一个子view全部展示在屏幕需要滑动的距离
// 得出 mScrollingOffset = 此次的滑动距离
int scrollingOffset = layoutState.mScrollingOffset;
int noRecycleSpace = layoutState.mNoRecycleSpace;//为0
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
// 这是往下滑
recycleViewsFromEnd(recycler, scrollingOffset, noRecycleSpace);
} else {
// 往上滑触发 也就是手指从下往上
recycleViewsFromStart(recycler, scrollingOffset, noRecycleSpace);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private void recycleViewsFromStart(RecyclerView.Recycler recycler, int scrollingOffset,
int noRecycleSpace) {
//scrollingOffset 此次的滑距离
if (scrollingOffset < 0) {
return;
}
// limit 此次的滑动距离
final int limit = scrollingOffset - noRecycleSpace;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
// 这里干了见很重要的事情,用来判断是否要回收那些看不到的view
// getDecoratedEnd 获取的是当前child的getBottom()的值。也就是child全部划出屏幕需要走的距离。见图:
// getTransformedEndWithDecoration 这个方法得到的结果 child.height - child.getTop
// 得到的结果还是 这个child划出屏幕所需要的距离。
if (mOrientationHelper.getDecoratedEnd(child) > limit
|| mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
recycleChildren(recycler, 0, i);
return;
}
}
}
img-0

ScrollVectorProvider

这是一个接口,在Recycleview出现惯性滑动得时候会被调用:

1
2
3
4
5
6
7
8
9
10
public interface ScrollVectorProvider {
/**
* 应该计算指向可以找到目标位置的方向的向量。
*LinearSmoothScroller使用此方法启动向目标位置的滚动。
*矢量的大小并不重要。 在被LinearSmoothScroller使用之前它总是被标准化。
*LayoutManager 不应该检查该位置是否存在于适配器中。
*/
@Nullable
PointF computeScrollVectorForPosition(int targetPosition);
}
1
LinearSmoothScroller

SnapHelper

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
private boolean snapFromFling(@NonNull RecyclerView.LayoutManager layoutManager, int velocityX,
int velocityY){
// 判断recycleview是否继承了接口ScrollVectorProvider
// 很明显recycleview继承了。
if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
return false;
}
// 创建对象LinearSmoothScroller
RecyclerView.SmoothScroller smoothScroller = createScroller(layoutManager);
if (smoothScroller == null) {
return false;
}

// 找到目标滚动到的位置
// 原理就是 先计算recycleview的中心点位置,然后遍历所有的孩子。找到那个孩子(child.getTop + child.高度/2)这个值距离recycleiew中心点最近和最远的那两个view
// 然后通过滑动防线判断是获取这两个哪个孩子的位置为最终位置 然后返回出来。
int targetPosition = findTargetSnapPosition(layoutManager, velocityX, velocityY);
if (targetPosition == RecyclerView.NO_POSITION) {
return false;
}

smoothScroller.setTargetPosition(targetPosition);
// 让recycleview滚动到指定位置 我们可以学习这部分代码让recycleview平滑的滚动到指定位置
layoutManager.startSmoothScroll(smoothScroller);
return true;
}

@Nullable
protected RecyclerView.SmoothScroller createScroller(RecyclerView.LayoutManager layoutManager) {
return createSnapScroller(layoutManager);
}

@Nullable
@Deprecated
protected LinearSmoothScroller createSnapScroller(RecyclerView.LayoutManager layoutManager) {
if (!(layoutManager instanceof RecyclerView.SmoothScroller.ScrollVectorProvider)) {
return null;
}
return new LinearSmoothScroller(mRecyclerView.getContext()) {
@Override
protected void onTargetFound(View targetView, RecyclerView.State state, Action action) {
if (mRecyclerView == null) {
return;
}
int[] snapDistances = calculateDistanceToFinalSnap(mRecyclerView.getLayoutManager(),
targetView);
final int dx = snapDistances[0];
final int dy = snapDistances[1];
final int time = calculateTimeForDeceleration(Math.max(Math.abs(dx), Math.abs(dy)));
if (time > 0) {
action.update(dx, dy, time, mDecelerateInterpolator);
}
}

@Override
protected float calculateSpeedPerPixel(DisplayMetrics displayMetrics) {
return MILLISECONDS_PER_INCH / displayMetrics.densityDpi;
}
};
}

PagerSnapHelper

研究下他是怎么通过手指头抬起得速度判断是否需要滑动到下一页:

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
// 这个方法的返回值就是目标item位置,最终recycleview停下的位置
@Override
public int findTargetSnapPosition(RecyclerView.LayoutManager layoutManager, int velocityX,
int velocityY) {
// 这个count是我们在adapter里面设置得总数。
final int itemCount = layoutManager.getItemCount();
if (itemCount == 0) {
return RecyclerView.NO_POSITION;
}
// 这个对象上面已经分析过了,分为垂直的和横向的
final OrientationHelper orientationHelper = getOrientationHelper(layoutManager);
if (orientationHelper == null) {
return RecyclerView.NO_POSITION;
}

//closestChildBeforeCenter: 距离recycleview中点上面最近的一个点
View closestChildBeforeCenter = null;
int distanceBefore = Integer.MIN_VALUE;
//closestChildAfterCenter: 距离recycleview中心点下面最近的一个点
View closestChildAfterCenter = null;
int distanceAfter = Integer.MAX_VALUE;

//找到中心之前的第一个视图和中心之后的第一个视图
// getChildCount的值是 recycleview这个viewGroup里面包含的孩子数量。和getItenCount的值不一样。
final int childCount = layoutManager.getChildCount();
// 通过这么一个循环。遍历了全部的子view用来寻找这个距离recycleview中心点之前最近的一个view 。 找出一个距离recycleview中心点下面最近的一个点。
for (int i = 0; i < childCount; i++) {
final View child = layoutManager.getChildAt(i);
if (child == null) {
continue;
}
// 找到item处理recycleview中间的位置。
// 计算方法 distance = (目标child距离recycleView的getTop() + child的高度/2) - (recycleview的高度/2)
final int distance = distanceToCenter(layoutManager, child, orientationHelper);

if (distance <= 0 && distance > distanceBefore) {
// distance小于0代表 这个child的中心点在recycleview的中心点上面
distanceBefore = distance;
closestChildBeforeCenter = child;
}
if (distance >= 0 && distance < distanceAfter) {
// distance小于0代表 这个child的中心点在recycleview的中心点下面
distanceAfter = distance;
closestChildAfterCenter = child;
}
}
// 上面一个循环走完 也就找到了两个距离中心点一上一下最近的点了。

// 返回第一个孩子的位置,从中心,在投掷的方向
// 如果这个layoutManager是支持横向滑动的然后如果 veloityX>0则返回true
// 否则 veloctyY>0 返回true
final boolean forwardDirection = isForwardFling(layoutManager, velocityX, velocityY);
// 然后通过滑动方向,判断是拿距离中心点上面最近的view还是拿距离中心点下面最近的view
if (forwardDirection && closestChildAfterCenter != null) {
return layoutManager.getPosition(closestChildAfterCenter);
} else if (!forwardDirection && closestChildBeforeCenter != null) {
return layoutManager.getPosition(closestChildBeforeCenter);
}

// 下面这里很少情况会触发。。以下不是正常情况会发生的
View visibleView = forwardDirection ? closestChildBeforeCenter : closestChildAfterCenter;
if (visibleView == null) {
return RecyclerView.NO_POSITION;
}
int visiblePosition = layoutManager.getPosition(visibleView);
int snapToPosition = visiblePosition
+ (isReverseLayout(layoutManager) == forwardDirection ? -1 : +1);

if (snapToPosition < 0 || snapToPosition >= itemCount) {
return RecyclerView.NO_POSITION;
}
return snapToPosition;
}

可以看到在recycleview内部是通过LayoutManager.getPosition的方法获取view的位置的。再看getPosition的源码。是通过view去获得他的viewHolder然后掉用viewHolder.getLayoutPosition方法获取的

自己想让recycleview滚动起来可以怎么做:

在看PagerSnapHelper的源码发现。

在实现惯性滑动的时候,recycleview的底层是通过这样的方法让recycleview滚动起来的。:

  1. 首先我们需要new一个Recycleview.SmoothScroller对象
  2. 给Recycleview.SmoothScroller对象设置一个目标滚动位置
  3. 调用layoutManager的startSmoothScroll(RecycleView.SmaoothScroller)方法
1
2
3
4
5
6
7
8
9
10
11
// 这部分全部代码在 SnaperHelper的 snapFromFling()方法里面。
// 创建Recycleview.SmoothScroller对象
RecyclerView.SmoothScroller smoothScroller = createScroller(layoutManager);
if (smoothScroller == null) {
return false;
}
int targetPosition = 10
// 设置滚动到的位置
smoothScroller.setTargetPosition(targetPosition);
// 然recycleview滚动起来
layoutManager.startSmoothScroll(smoothScroller);

LayoutManger.startSmaoothScroll(SmoothScroller smoothScroller)

1
2
3
4
5
6
7
8
public void startSmoothScroll(SmoothScroller smoothScroller) {
if (mSmoothScroller != null && smoothScroller != mSmoothScroller
&& mSmoothScroller.isRunning()) {
mSmoothScroller.stop();
}
mSmoothScroller = smoothScroller;
mSmoothScroller.start(mRecyclerView, this);
}
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
// Recycleview
void start(RecyclerView recyclerView, LayoutManager layoutManager) {
recyclerView.mViewFlinger.stop();
mRecyclerView = recyclerView;
mLayoutManager = layoutManager;
if (mTargetPosition == RecyclerView.NO_POSITION) {
throw new IllegalArgumentException("Invalid target position");
}
// 可以看到在触发滑动的时候才会给mTargetPostion参数赋值
mRecyclerView.mState.mTargetPosition = mTargetPosition;
mRunning = true;
mPendingInitialRun = true;
mTargetView = findViewByPosition(getTargetPosition());
onStart();
// 发送了动画播放消息
// 这个mViewFlinger是一个继承Runnable的类。显而易见这个是一个消息
mRecyclerView.mViewFlinger.postOnAnimation();

mStarted = true;
}
void postOnAnimation() {
//mEatRunOnAnimationRequest 这个参数是延迟播放的意思 如果要延迟 这个参数为true
// 默认为false
if (mEatRunOnAnimationRequest) {
mReSchedulePostAnimationCallback = true;
} else {
// 进到这里
internalPostOnAnimation();
}
}

private void internalPostOnAnimation() {
// 移除message消息
removeCallbacks(this);
// 向消息队列里面发送一个关于滚动视图的消息
// 这里把消息插入到了ViewRootImpl里面触发 这里也很有研究价值
ViewCompat.postOnAnimation(RecyclerView.this, this);
}

// 可以看到一个滚动的message被发送了出去。所以可以看类ViewFlinger的run方法:

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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
//ViewFlinger 他是recycleview的内部类 
public void run() {
if (mLayout == null) {
stop();
return; // no layout, cannot scroll.
}

mReSchedulePostAnimationCallback = false;
mEatRunOnAnimationRequest = true;

consumePendingUpdateOperations();

// TODO(72745539): After reviewing the code, it seems to me we may actually want to
// update the reference to the OverScroller after onAnimation. It looks to me like
// it is possible that a new OverScroller could be created (due to a new Interpolator
// being used), when the current OverScroller knows it's done after
// scroller.computeScrollOffset() is called. If that happens, and we don't update the
// reference, it seems to me that we could prematurely stop the newly created scroller
// due to setScrollState(SCROLL_STATE_IDLE) being called below.

// Keep a local reference so that if it is changed during onAnimation method, it won't
// cause unexpected behaviors
final OverScroller scroller = mOverScroller;
if (scroller.computeScrollOffset()) {
final int x = scroller.getCurrX();
final int y = scroller.getCurrY();
int unconsumedX = x - mLastFlingX;
int unconsumedY = y - mLastFlingY;
mLastFlingX = x;
mLastFlingY = y;
int consumedX = 0;
int consumedY = 0;

// Nested Pre Scroll
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
if (dispatchNestedPreScroll(unconsumedX, unconsumedY, mReusableIntPair, null,
TYPE_NON_TOUCH)) {
unconsumedX -= mReusableIntPair[0];
unconsumedY -= mReusableIntPair[1];
}

// Based on movement, we may want to trigger the hiding of existing over scroll
// glows.
if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
considerReleasingGlowsOnScroll(unconsumedX, unconsumedY);
}

// Local Scroll
if (mAdapter != null) {
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
scrollStep(unconsumedX, unconsumedY, mReusableIntPair);
consumedX = mReusableIntPair[0];
consumedY = mReusableIntPair[1];
unconsumedX -= consumedX;
unconsumedY -= consumedY;

// If SmoothScroller exists, this ViewFlinger was started by it, so we must
// report back to SmoothScroller.
SmoothScroller smoothScroller = mLayout.mSmoothScroller;
if (smoothScroller != null && !smoothScroller.isPendingInitialRun()
&& smoothScroller.isRunning()) {
final int adapterSize = mState.getItemCount();
if (adapterSize == 0) {
smoothScroller.stop();
} else if (smoothScroller.getTargetPosition() >= adapterSize) {
smoothScroller.setTargetPosition(adapterSize - 1);
smoothScroller.onAnimation(consumedX, consumedY);
} else {
smoothScroller.onAnimation(consumedX, consumedY);
}
}
}

if (!mItemDecorations.isEmpty()) {
invalidate();
}

// Nested Post Scroll
mReusableIntPair[0] = 0;
mReusableIntPair[1] = 0;
dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, null,
TYPE_NON_TOUCH, mReusableIntPair);
unconsumedX -= mReusableIntPair[0];
unconsumedY -= mReusableIntPair[1];

if (consumedX != 0 || consumedY != 0) {
dispatchOnScrolled(consumedX, consumedY);
}

if (!awakenScrollBars()) {
invalidate();
}

// We are done scrolling if scroller is finished, or for both the x and y dimension,
// we are done scrolling or we can't scroll further (we know we can't scroll further
// when we have unconsumed scroll distance). It's possible that we don't need
// to also check for scroller.isFinished() at all, but no harm in doing so in case
// of old bugs in Overscroller.
boolean scrollerFinishedX = scroller.getCurrX() == scroller.getFinalX();
boolean scrollerFinishedY = scroller.getCurrY() == scroller.getFinalY();
final boolean doneScrolling = scroller.isFinished()
|| ((scrollerFinishedX || unconsumedX != 0)
&& (scrollerFinishedY || unconsumedY != 0));

// Get the current smoothScroller. It may have changed by this point and we need to
// make sure we don't stop scrolling if it has changed and it's pending an initial
// run.
SmoothScroller smoothScroller = mLayout.mSmoothScroller;
boolean smoothScrollerPending =
smoothScroller != null && smoothScroller.isPendingInitialRun();

if (!smoothScrollerPending && doneScrolling) {
// If we are done scrolling and the layout's SmoothScroller is not pending,
// do the things we do at the end of a scroll and don't postOnAnimation.

if (getOverScrollMode() != View.OVER_SCROLL_NEVER) {
final int vel = (int) scroller.getCurrVelocity();
int velX = unconsumedX < 0 ? -vel : unconsumedX > 0 ? vel : 0;
int velY = unconsumedY < 0 ? -vel : unconsumedY > 0 ? vel : 0;
absorbGlows(velX, velY);
}

if (ALLOW_THREAD_GAP_WORK) {
mPrefetchRegistry.clearPrefetchPositions();
}
} else {
// Otherwise continue the scroll.

postOnAnimation();
if (mGapWorker != null) {
mGapWorker.postFromTraversal(RecyclerView.this, consumedX, consumedY);
}
}
}

SmoothScroller smoothScroller = mLayout.mSmoothScroller;
// call this after the onAnimation is complete not to have inconsistent callbacks etc.
if (smoothScroller != null && smoothScroller.isPendingInitialRun()) {
smoothScroller.onAnimation(0, 0);
}

mEatRunOnAnimationRequest = false;
if (mReSchedulePostAnimationCallback) {
internalPostOnAnimation();
} else {
setScrollState(SCROLL_STATE_IDLE);
stopNestedScroll(TYPE_NON_TOUCH);
}
}