掘金博客记录
adapter.notifyDataSetChanaged()
引起的刷新我们假设
recycleView
的初始状态是没有数据的,然后往数据源中加入数据后,调用adapter.notifyDataSetChanged()
来引起RecyclerView
的刷新:1
2data.addAll(data)
adapter.notifyDataSetChanged()adapter.notifyDataSetChanged()
时,会引起recycleView
重新布局(requestLayout
).因此从onLayout()
方法开始:1
2
3
4
5
6
7
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
28void 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
10void dispatchLayout() {
...
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
dispatchLayoutStep2();
} else if (数据变化 || 布局变化) {
dispatchLayoutStep2();
}
dispatchLayoutStep3();
}上面是裁掉了一些代码,可以看到整个布局过程总共分为3步,下面3步对应的方法:
1
2
3STEP_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
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
最重要的两个属性是; mCoordinate
和 mPosition
. 找到锚点 View
后就会通过anchorInfo.assignFromView()
方法来设置这两个属性:
1 | public void assignFromView(View child, int position) { |
1 | mCoordinate: 就是锚点View的 Y(x) 坐标去掉 RecycleView 的 padding. |
当确定好 AnchorInfo
后,需要根据 AnchorInfo
来确定 RecycleView
当前可用于布局的空间,然后来摆放子View。以布局方向为 start to end
正常方向为例,这里的锚点View其实就是 RecyclerView
最顶部的 View:
1 | // fill towards end (1) |
上面我标注了(1)(2),1次布局是由这两部分组成的,具体如下图:
fill towards end
确定可用布局空间
在 fill
之前,需要先确定 从锚点View
到 RecyclerView
底部有多少可用空间。是通过 updateLayoutStateToFillEnd
方法:
1 | updateLayoutStateToFillEnd(anchorInfo.mPosition, anchorInfo.mCoordinate); |
mLayoutState
是 LinearLayoutmanager
用来保存布局状态的一个对象。 mLayoutState.mAvailable
就是用来表示 有多少空间可用布局。mOrientationHelper.getEndAfterPadding() - offset
其实大致可以理解为RecycleView
的高度。所以这里可用布局空间 mLayoutState.mAvailable
就是 ReycleView
的高度。
摆放子View
接下来继续看 LinearLayoutManager.fill()
方法,这个方法是布局的核心方法,是用来向 RecycleView
中添加子View的方法:
1 | int fill(RecyclerView.Recycler recycler, LayoutState layoutState, |
这个方法的核心就是调用 layoutChunk()
来不断消耗layoutState.mAvailable
直到消耗完毕,继续看下 layoutChunk()
方法:
1 | void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,LayoutState layoutState, LayoutChunkResult result) { |
到这里其实就完成了上面的 fill towards end
:
1 | updateLayoutStateToFillEnd(mAnchorInfo)// 确定布局可用空间 |
fill towards start
就是从 锚点View
向 RecycleView
顶部来摆放子View
,具体逻辑类似 fill towards end.
RecyclerView 滑动时的刷新逻辑
在不加载新的数据情况下, RecycleView 在滑动时是如何展示 子View的,即下面这种状态:
下面就来分析一个 3,4 和 12,13是如何展示的。
RecycleView 在 OnTouchEvent 对滑动事件做了监听,然后派发到 scrollStep() 方法: