RecyclerView绘制原理

RecyclerView基本使用

1
2
3
4
5
6
7
8
9
10
//首先设置RecyclerView的布局管理模式
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mAdapter = new MyAdapter(getData());
//设置Item项的UI装饰器
mRecyclerView.addItemDecoration(new RecyclerItemDecoration(LinearLayoutManager.VERTICAL));
//设置Item项的不同操作的动画
mRecyclerView.setItemAnimator(new DefaultItemAnimator());
//设置数据开始装配
mRecyclerView.setAdapter(mAdapter);
RecyclerView原理分析

Adapter数据适配

RecyclerView.Adapter类中有一个很重要的属性:

1
2
3
//Adapter中被观察对象
Observale<AdapterDataObserver>
private final AdapterDataObservable mObservable = new AdapterDataObservable();

RecyclerView中也有一个很重要的属性

1
2
//数据观察者, AdapterDataObserver实例
private final RecyclerViewDataObserver mObserver = new RecyclerViewDataObserver();

当使用recyclerView.setAdapter(data)设置数据时,会调用以下方法使得RecyclerView成为Adapter的观察者(间接):

1
2
3
4
5
6
7
8
9
10
11
private void setAdapterInternal(Adapter adapter,
boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
......
if (adapter != null) {
//通过此处添加观察者,此时RecyclerView就会对Adapter中的数据进行观察监听
adapter.registerAdapterDataObserver(mObserver);
......
}
......
}

通常当我们改变Adapter中的数据源时,一般都会通过调用Adapter.notifyDataSetChanged()方法来刷新列表,我们来看看这个方法的实现,看看Adapter是如何通过这个方法来刷新列表的:

1
2
3
public final void notifyDataSetChanged() {
mObservable.notifyChanged();
}

我们接着看AdapterDataObservable.notifyChanged()方法实现:

1
2
3
4
5
6
//mObservers是Observable中的属性,是一个ArrayList<T>
public void notifyChanged() {
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}

我们再来看RecyclerViewDataObserver.onChanged()方法:

1
2
3
4
5
6
7
8
@Override
public void onChanged() {
......
//Adapter目前没有待更新的数据
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}

看到requestLayout()这个方法,我们就明白了,调用此方法后系统会重新measure, layout, draw,这样列表视图就会被更新。

RecyclerView.onMeasure()

我们来看看RecyclerView的测量方法onMeasure

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
......
if (mLayout.mAutoMeasure) {
final int widthMode = MeasureSpec.getMode(widthSpec);
final int heightMode = MeasureSpec.getMode(heightSpec);
final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
//委托给LayoutManager来进行测量
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (skipMeasure || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
dispatchLayoutStep2();
mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
......
}
}
......
}

上面onMeasure方法中mLayout变量就是我们上面设置的LinearLayoutManager实例,而LinearLayoutManager的构造函数中给变量mAutoMeasure值设置为true,因此测量时就会执行上面的代码,通过上面的方法我们可以看到此处测量分为两种:

RecyclerView的宽高设置为match_parent或具体值的时候,skipMeasure=true,此时会只需要测量其自身的宽高就可以知道RecyclerView的大小,这时是onMeasure方法测量结束。
当RecyclerView的宽高设置为wrap_content时,skipMeasure=falseonMeasure会继续执行下面的dispatchLayoutStep2(),其实就是测量RecyclerView的子视图的大小最终确定RecyclerView的实际大小,这种情况真正的测量操作都是在方法dispatchLayoutStep2()里执行的:

1
2
3
4
5
6
7
8
private void dispatchLayoutStep2() {
......
mState.mItemCount = mAdapter.getItemCount();
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
......
}

从这里也可以看出RecyclerView真正的测量是委托给LayoutManager在处理,我们看看LinearLayoutManageronLayoutChildren方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
......
if (mAnchorInfo.mLayoutFromEnd) {
...
fill(recycler, mLayoutState, state, false);
......
} else {
......
fill(recycler, mLayoutState, state, false);
......
}
......
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
......
}

很明显可以看到,最终执行了fill()方法:

1
2
3
4
5
6
7
8
9
10
int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) {
......
LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
layoutChunk(recycler, state, layoutState, layoutChunkResult);
......
}
......
}

上面的while判断条件中remainingSpace可以理解为当前列表中是否还有多余的位置可用于添加绘制child,而layoutState.hasMore(state)则是判断当前绘制的child索引位置是否在Adapter数据范围内

1
2
3
boolean hasMore(RecyclerView.State state) {
return mCurrentPosition >= 0 && mCurrentPosition < state.getItemCount();
}

再来看上面的layoutChunk()方法:

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
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler);
......
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
measureChildWithMargins(view, 0, 0);
......
// To calculate correct layout position, we subtract margins.
layoutDecorated(view, left + params.leftMargin, top + params.topMargin, right - params.rightMargin, bottom - params.bottomMargin);
......
}

上面的方法中addViewaddDisappearingView最终都是调用的RecyclerViewaddView方法,也就是将子child添加到RecyclerView中。

我们再来看看View view = layoutState.next(recycler);这行代码的实现:

1
2
3
4
5
6
7
8
9
10
11
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList();
}
final View view = recycler.getViewForPosition(mCurrentPosition);
//获取某个位置需要展示的View
mCurrentPosition += mItemDirection;
//将当前绘制的child的索引下移一位,配合while循环
return view;
}

我们看看上面的获取position位置的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
View getViewForPosition(int position, boolean dryRun) {
......
ViewHolder holder = null;
// 0) If there is a changed scrap, try to find from there
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrap = holder != null;
}
// 1) Find from scrap by position
if (holder == null) {
holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);
......
}
if (holder == null) {
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
......
final int type = mAdapter.getItemViewType(offsetPosition);
// 2) Find from scrap via stable ids, if exists
if (mAdapter.hasStableIds()) {
holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
......
}
//mViewCacheExtension的缓存是由开发者自己实现来控制ViewHolder的缓存策略
if (holder == null && mViewCacheExtension != null) {
// We are NOT sending the offsetPosition because LayoutManager does not
// know it.
final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = getChildViewHolder(view);
......
}
}
if (holder == null) { // fallback to recycler
......
holder = getRecycledViewPool().getRecycledView(type);
......
}
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type);
......
}
}
......
boolean bound = false;
if (mState.isPreLayout() && holder.isBound()) {
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
......
holder.mOwnerRecyclerView = RecyclerView.this;
//此处就是调用Adapter中bindViewHolder方法
mAdapter.bindViewHolder(holder, offsetPosition);
......
}
......
return holder.itemView;
}

将指定位置的View获取得到之后添加到RecyclerView中,紧接着再来看后面执行的measureChildWithMargins方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
public void measureChildWithMargins(View child, int widthUsed, int heightUsed) {
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
//通过ItemDecorate获取offset
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;
final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(), getPaddingLeft() + getPaddingRight() + lp.leftMargin + lp.rightMargin + widthUsed, lp.width, canScrollHorizontally());
final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(), getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin + heightUsed, lp.height, canScrollVertically());
if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
child.measure(widthSpec, heightSpec);
}
}

该方法中调用mRecyclerView.getItemDecorInsetsForChild(child);获取child的offset,然后对child重新测量绘制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Rect getItemDecorInsetsForChild(View child) {
......
final int decorCount = mItemDecorations.size();
for (int i = 0; i < decorCount; i++) {
mTempRect.set(0, 0, 0, 0);
mItemDecorations.get(i).getItemOffsets(mTempRect, child, this, mState);
insets.left += mTempRect.left;
insets.top += mTempRect.top;
insets.right += mTempRect.right;
insets.bottom += mTempRect.bottom;
}
lp.mInsetsDirty = false;
return insets;
}

这个里面的mItemDecorations就是文章开头例子中我通过mRecyclerView.addItemDecoration(new RecyclerItemDecoration(LinearLayoutManager.VERTICAL));添加的Item装饰器

1
2
3
4
public void layoutDecorated(View child, int left, int top, int right, int bottom) {
final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
child.layout(left + insets.left, top + insets.top, right - insets.right, bottom - insets.bottom);
}

可以看到layoutDecorated方法中直接调用了View的layout方法对child视图进行layout布局。

到此RecyclerView列表中Item项视图的measure和layout实际上已经完成。

RecyclerView.onLayout

看完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
@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;
}
void dispatchLayout() {
......
mState.mIsMeasuring = false;
if (mState.mLayoutStep == State.STEP_START) {
......
dispatchLayoutStep2();
} else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() || mLayout.getHeight() != getHeight()) {
......
dispatchLayoutStep2();
} else {
// always make sure we sync them (to ensure mode is exact)
mLayout.setExactMeasureSpecsFrom(this);
}
dispatchLayoutStep3();
}

通过dispatchLayout方法可以看到onLayout中又执行了我们前面分析过的dispatchLayoutStep2()方法,在最后又执行了一个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
private void dispatchLayoutStep3() {
......
if (mState.mRunSimpleAnimations) {
for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
......
final ItemHolderInfo animationInfo = mItemAnimator
.recordPostLayoutInformation(mState, holder);
......
if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
......
if (oldDisappearing && oldChangeViewHolder == holder) {
//此处会执行动画
// run disappear animation instead of change
mViewInfoStore.addToPostLayout(holder, animationInfo);
} else {
......
if (preInfo == null) {
handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
} else {
//此方法最终调用DefaultItemAnimate的相关动画
animateChange(oldChangeViewHolder, holder, preInfo, postInfo, oldDisappearing, newDisappearing);
}
}
} else {
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
}
// Step 4: Process view info lists and trigger animations
mViewInfoStore.process(mViewInfoProcessCallback);
}
......
}

上面的方法中调用了ItemAnimation动画类的相关方法

RecyclerView.onDraw

1
2
3
4
5
6
7
8
9
@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);
}
}

可以看到ItemDecorationonDraw方法是在此处调用

1
2
3
4
5
6
7
8
9
10
@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);
}
......
}

这个地方可以看到ItemDecorationonDrawOver方法是在此处调用

到这里,RecyclerView使用过程中比较常用的几个类(LayoutManager, ItemDecoration, ItemAnimation)的主要作用及使用场景有了个大概的了解。

在RecyclerView中是没有为我们内置Item的单击和长按事件监听接口的,一般为Item设置单击和长按监听都是是直接在Adapter初始化Item视图时,为我们的Item视图直接设置单击监听和长按监听,这种方式与Adapter的耦合度比较高,而且频繁的为view设置监听对象,感觉不太好。其实RecyclerView中为我们提供了一个类OnItemTouchListener通过这个类再结合手势GestureDetector完全可以实现一个耦合度更低复用度更高的单击和长按监听。我们再来看看OnItemTouchListener的实现方式:

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
@Override
public boolean onTouchEvent(MotionEvent e) {
......
if (dispatchOnItemTouch(e)) {
cancelTouch();
return true;
}
......
}
private boolean dispatchOnItemTouch(MotionEvent e) {
final int action = e.getAction();
if (mActiveOnItemTouchListener != null) {
if (action == MotionEvent.ACTION_DOWN) {
// Stale state from a previous gesture, we're starting a new one. Clear it.
mActiveOnItemTouchListener = null;
} else {
//此处即调用OnItemTouchListener的方法
mActiveOnItemTouchListener.onTouchEvent(this, e);
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
// Clean up for the next gesture.
mActiveOnItemTouchListener = null;
}
return true;
}
}
// Listeners will have already received the ACTION_DOWN via dispatchOnItemTouchIntercept
// as called from onInterceptTouchEvent; skip it.
if (action != MotionEvent.ACTION_DOWN) {
final int listenerCount = mOnItemTouchListeners.size();
for (int i = 0; i < listenerCount; i++) {
final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
//此处即调用OnItemTouchListener的方法
if (listener.onInterceptTouchEvent(this, e)) {
mActiveOnItemTouchListener = listener;
return true;
}
}
}
return false;
}
@Override
public boolean onInterceptTouchEvent(MotionEvent e) {
......
if (dispatchOnItemTouchIntercept(e)) {
cancelTouch();
return true;
}
......
}
private boolean dispatchOnItemTouchIntercept(MotionEvent e) {
final int action = e.getAction();
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_DOWN) {
mActiveOnItemTouchListener = null;
}
final int listenerCount = mOnItemTouchListeners.size();
for (int i = 0; i < listenerCount; i++) {
final OnItemTouchListener listener = mOnItemTouchListeners.get(i);
if (listener.onInterceptTouchEvent(this, e) && action != MotionEvent.ACTION_CANCEL) {
mActiveOnItemTouchListener = listener;
return true;
}
}
return false;
}


原创文章,本文采用知识共享署名 2.5(中国大陆许可协议)进行许可,欢迎转载,但转载请注明来自ittiger.cn,并保证转载后文章内容的完整性。本人(laohu)保留所有版权相关权利。



评论