上一篇介紹基本使用,這一篇我們深入分析RecyclerView內(nèi)部被渲染的流程航徙,在這里我們從三個方面來了解流程锨侯,具體三個方面如下:
- 啟動渲染:一般首次渲染的時候。
- 滑動渲染: 手指觸摸屏幕滑動直到離開的過程早抠。
- 通知渲染:一般調(diào)用
notifyDataSetChanged() 或者 notifyItemChanged()
時候。
1撬讽、啟動渲染
RecyclerView繼承ViewGroup蕊连,同樣準(zhǔn)守View的繪制過程悬垃,所以也會走onMeasure、onLayout甘苍、onDraw方法尝蠕,我們先來看看RecyclerView的onMeasure方法。
Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.isAutoMeasureEnabled()) {
//...省略
// modes in both dimensions are EXACTLY.
mLastAutoMeasureSkippedDueToExact =
widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY
if (mLastAutoMeasureSkippedDueToExact || mAdapter == null) {
return;
}
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
mLayout.setMeasureSpecs(widthSpec, heightSpec);
mState.mIsMeasuring = true;
dispatchLayoutStep2();
//...省略
}else{
//...省略
}
}
我們只看核心代碼载庭,現(xiàn)在我們以LinearLayoutManager為例子看彼,LinearLayoutManager重寫了isAutoMeasureEnabled()方法并且返回true,這樣確定了RecyclerView的繪制走了自動布局的邏輯囚聚。如果Recyclerview確定了畫布大芯搁拧(寬高都是MeasureSpec.EXACTLY)直接return,測量結(jié)束靡挥。重點(diǎn)還是dispatchLayoutStep1()序矩、dispatchLayoutStep2()邏輯。其中dispatchLayoutStep1()的代碼如下:
/**
* The first step of a layout where we;
* - process adapter updates 處理adapter更新
* - decide which animation should run 決定哪個執(zhí)行動畫
* - save information about current views 保存當(dāng)前view的信息
* - If necessary, run predictive layout and save its information 是否進(jìn)行預(yù)加載并且保存預(yù)加載信息
*/
private void dispatchLayoutStep1() {
mState.mIsMeasuring = false;
processAdapterUpdatesAndSetAnimationFlags();
saveFocusInfo();
//...省略
if (mState.mRunSimpleAnimations) {
//...省略
}
if (mState.mRunPredictiveAnimations) {
//...省略
}
// ..省略
mState.mLayoutStep = State.STEP_LAYOUT;
}
dispatchLayoutStep1()主要還是更新狀態(tài)(mState)和視圖(mViewInfoStore)為下一步繪制做準(zhǔn)備跋破。接下來看dispatchLayoutStep2()方法,代碼如下:
/**
* The second layout step where we do the actual layout of the views for the final state.
* This step might be run multiple times if necessary (e.g. measure).
*/
private void dispatchLayoutStep2() {
//..省略
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
//..省略
mState.mLayoutStep = State.STEP_ANIMATIONS;
}
這個方法更新了部分State,核心還是onLayoutChildren()方法瓶蝴,由LayoutManager代理毒返,其他onLayoutChildren()實現(xiàn)了RecyclerView的絕大部分視圖渲染邏輯。我們這邊以LinearLayoutMananger為例子進(jìn)行繪制分析舷手,接下來看看onLayoutChildren()的代碼如下:
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
// layout algorithm:
// 1) by checking children and other variables, find an anchor coordinate and an anchor
// item position.
// 2) fill towards start, stacking from bottom
// 3) fill towards end, stacking from top
// 4) scroll to fulfill requirements like stack from bottom.
// create layout state
//以上注釋已經(jīng)說明了繪制算法:1拧簸、先確定錨點(diǎn)(anchor)2、布局方向由下往上或者由上往下繪制男窟。
//...省略
if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
|| mPendingSavedState != null) { //首次繪制確定錨點(diǎn)盆赤、滿足次條件。
mAnchorInfo.reset();
//確定繪制步驟歉眷,決定由上往下還是由下往上繪制牺六。
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// calculate anchor position and coordinate 計算anchor的position 和 coordinate
//計算anchor由一套規(guī)則,anchor依次由滑動mPendingSavedState汗捡、聚焦view來確定淑际,如果未發(fā)現(xiàn)初始化anchor(position = 0)。
updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
mAnchorInfo.mValid = true;
} else if (focused != null && (mOrientationHelper.getDecoratedStart(focused)
>= mOrientationHelper.getEndAfterPadding()
|| mOrientationHelper.getDecoratedEnd(focused)
<= mOrientationHelper.getStartAfterPadding())) {
//在有focused聚焦視圖情況下扇住,滿足條件更新錨點(diǎn)信息春缕。
mAnchorInfo.assignFromViewAndKeepVisibleRect(focused, getPosition(focused));
}
//...省略
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
fill(recycler, mLayoutState, state, false);
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
fill(recycler, mLayoutState, state, false);
} else {
// fill towards end
updateLayoutStateToFillEnd(mAnchorInfo);
fill(recycler, mLayoutState, state, false);
// fill towards start
updateLayoutStateToFillStart(mAnchorInfo);
fill(recycler, mLayoutState, state, false);
}
//...省略
}
以上代碼體現(xiàn)錨點(diǎn)的計算非常重要,我們來看一下錨點(diǎn)(AnchorInfo)和布局狀態(tài)(LayoutState)重要屬性艘蹋。
/**
* Simple data class to keep Anchor information
*/
static class AnchorInfo {
OrientationHelper mOrientationHelper; //計算start锄贼,end等輔助類
int mPosition; //Anchor 啟點(diǎn)的位置
int mCoordinate; //Anchor Y軸上起始繪制偏移量
boolean mLayoutFromEnd; //布局方向
boolean mValid; //錨點(diǎn)是否合法
}
/**
* Helper class that keeps temporary state while {LayoutManager} is filling out the empty
* space.
*/
static class LayoutState {
/**
* Current position on the adapter to get the next item.
* 當(dāng)前位置
*/
int mCurrentPosition;
/**
* Number of pixels that we should fill, in the layout direction.
* 布局方向有多少空間需要被填充
*/
int mAvailable;
/**
* Used if you want to pre-layout items that are not yet visible.
* 提前預(yù)加載空間
* The difference with {@link #mAvailable} is that, when recycling, distance laid out for
* {@link #mExtraFillSpace} is not considered to avoid recycling visible children.
*/
int mExtraFillSpace = 0;
/**
* Pixel offset where layout should start
* 開始布局的偏移位置
*/
int mOffset;
/**
* Defines the direction in which the layout is filled.
* 布局方向LAYOUT_START或者LAYOUT_END
* Should be {@link #LAYOUT_START} or {@link #LAYOUT_END}
*/
int mLayoutDirection;
/**
* Used when LayoutState is constructed in a scrolling state.
* 滾動的距離
* It should be set the amount of scrolling we can make without creating a new view.
* Settings this is required for efficient view recycling.
*/
int mScrollingOffset;
}
了解重要屬性之后,下面我們通過圖片來表達(dá)核心的繪制邏輯女阀。
圖一為首次RecyclerView繪制的時候宅荤,AnchorInfo{mPosition = 0,mCoordinate = 0} 屑迂,mLayoutDirection = LayoutState.LAYOUT_START 時候往上繪制空間為0,所以只考慮往下繪制(mAvailable + mExtraFillSpace)膘侮。圖二為多數(shù)滑動或者聚焦view(比如resume界面)的時候渲染經(jīng)過屈糊。在onLayoutChildren方法中調(diào)用updateLayoutStateToFillStart或者updateLayoutStateToFillEnd將計算結(jié)果更新至LayoutState,最后交付給fill進(jìn)行填充繪制琼了。
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
//...省略
int remainingSpace = layoutState.mAvailable + layoutState.mExtraFillSpace;
LayoutChunkResult layoutChunkResult = mLayoutChunkResult;
while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunk(recycler, state, layoutState, layoutChunkResult);
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;
// we keep a separate remaining space because mAvailable is important for recycling
remainingSpace -= layoutChunkResult.mConsumed;
}
//...省略
}
}
protected static class LayoutChunkResult {
public int mConsumed; //item高度
//...省略
}
fill代碼中不斷計算remainingSpace剩余高度逻锐,當(dāng)remainingSpace需要填充的時候調(diào)用layoutChunk()方法進(jìn)行item繪制,并且不斷更新對應(yīng)mConsumed雕薪,接下來我們看下layoutChunk方法昧诱。
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler); //這個方法非常重要,直接包含了recyclerview的緩存機(jī)制所袁,此處暫時沒進(jìn)行解釋盏档,緩存講解的時候再來看這個方法。
RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams();
//首次繪制layoutState.mScrapList == null
if (layoutState.mScrapList == null) {
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addView(view);
} else {
addView(view, 0);
}
} else {
//此處暫時不看燥爷,這邊預(yù)加載動畫相關(guān)的view添加蜈亩。
if (mShouldReverseLayout == (layoutState.mLayoutDirection
== LayoutState.LAYOUT_START)) {
addDisappearingView(view);
} else {
addDisappearingView(view, 0);
}
}
measureChildWithMargins(view, 0, 0);
result.mConsumed = mOrientationHelper.getDecoratedMeasurement(view);
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
layoutDecoratedWithMargins(view, left, top, right, bottom);
//...省略
}
以上代碼將item繪制出來并且addview到recyclerview中,并且通過layoutDecoratedWithMargins調(diào)整到最終的位置前翎。到此處我們來看一下item的包裝類稚配,補(bǔ)充下OrientationHelper,這個類協(xié)助remainingSpace空間的計算港华。
2道川、滑動渲染
滑動分為兩種,一種是正沉⒁耍滑動冒萄,一種是快速滑動(Flinger),以下分析這兩種滑動代碼橙数。
@Override
public boolean onTouchEvent(MotionEvent e) {
//...省略
switch (action) {
case MotionEvent.ACTION_DOWN: {
mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f);
mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f);
}
//...省略
case MotionEvent.ACTION_MOVE: {
if (mScrollState != SCROLL_STATE_DRAGGING) {
if (canScrollHorizontally) {
if (dx > 0) {
dx = Math.max(0, dx - mTouchSlop);
} else {
dx = Math.min(0, dx + mTouchSlop);
}
if (dx != 0) {
startScroll = true;
}
}
if (canScrollVertically) {
if (dy > 0) {
dy = Math.max(0, dy - mTouchSlop);
} else {
dy = Math.min(0, dy + mTouchSlop);
}
if (dy != 0) {
startScroll = true;
}
}
}
}
//...省略
if (mScrollState == SCROLL_STATE_DRAGGING) {
mLastTouchX = x - mScrollOffset[0];
mLastTouchY = y - mScrollOffset[1];
if (scrollByInternal(
canScrollHorizontally ? dx : 0,
canScrollVertically ? dy : 0,
vtev)) {
getParent().requestDisallowInterceptTouchEvent(true);
}
}
//...省略
case MotionEvent.ACTION_UP: {
//...省略
final float yvel = canScrollVertically ?
-VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId) : 0;
if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) {
setScrollState(SCROLL_STATE_IDLE);
}
resetTouch();
}
//...省略
}
以上代碼為手指觸摸到屏幕后尊流,記錄mLastTouchX、mLastTouchY商模,經(jīng)過ACTION_MOVE奠旺,計算dy或者dx,并且mTouchSlop為滑動閥值(這個值可初始化時候決定施流,能決定滑動偏移值)响疚,dy或者dx大于這個值觸發(fā)scrollByInternal方法,我們來看一下scrollByInternal方法瞪醋。
boolean scrollByInternal(int x, int y, MotionEvent ev, int type) {
//...省略
if (mAdapter != null) {
//...省略
scrollStep(x, y, mReusableIntPair);
//...省略
}
//...省略
}
在onTouchEvent中忿晕,當(dāng)ACTION_UP手指抬起來的時候,經(jīng)過VelocityTrackerCompat根據(jù)初速度來計算是否觸發(fā)fling()事件银受,我們先來看看fling的調(diào)用過程践盼。
public boolean fling(int velocityX, int velocityY) {
//...省略
velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity));
velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity));
mViewFlinger.fling(velocityX, velocityY);
//...省略
}
接著看ViewFlinger中的fling鸦采、postOnAnimation、internalPostOnAnimation方法
public void fling(int velocityX, int velocityY) {
//...省略
postOnAnimation();
//...省略
}
void postOnAnimation() {
if (mEatRunOnAnimationRequest) {
mReSchedulePostAnimationCallback = true;
} else {
internalPostOnAnimation();
}
}
private void internalPostOnAnimation() {
removeCallbacks(this);
ViewCompat.postOnAnimation(RecyclerView.this, this);
}
最終調(diào)用的是ViewFlinger方法中的run方法咕幻。
@Override
public void run() {
//...省略
if (mAdapter != null) {
//...省略
scrollStep(unconsumedX, unconsumedY, mReusableIntPair);
//...省略
}
//...省略
}
接下來分析scrollStep方法
void scrollStep(int dx, int dy, @Nullable int[] consumed) {
//...省略
if (dx != 0) {
consumedX = mLayout.scrollHorizontallyBy(dx, mRecycler, mState);
}
if (dy != 0) {
consumedY = mLayout.scrollVerticallyBy(dy, mRecycler, mState);
}
//...省略
}
最終調(diào)用了LayoutMananger中的scrollHorizontallyBy和scrollVerticallyBy方法渔伯,我們以LinearLayoutMananger為例子。以下分析LinearLayoutMananger中scrollHorizontallyBy為例子肄程。
/**
* {@inheritDoc}
*/
@Override
public int scrollHorizontallyBy(int dx, RecyclerView.Recycler recycler,
RecyclerView.State state) {
if (mOrientation == VERTICAL) {
return 0;
}
return scrollBy(dx, recycler, state);
}
int scrollBy(int delta, RecyclerView.Recycler recycler, RecyclerView.State state) {
//...省略
updateLayoutState(layoutDirection, absDelta, true, state);
final int consumed = mLayoutState.mScrollingOffset
+ fill(recycler, mLayoutState, state, false);
//...省略
}
到此處就可以看到updateLayoutState和fill方法锣吼,通過這兩個方法不斷的測量可繪空間并且填充,可繪制的空間加上了滑動的偏移量蓝厌。綜述完成了滑動繪制的講述玄叠。
3、通知渲染
通知渲染也有兩種方式:notifyDataSetChanged全局刷新和notifyItemChanged局部刷新拓提。
- 先來看看notifyDataSetChanged()
public final void notifyDataSetChanged() {
mObservable.notifyChanged();
}
public void notifyChanged() {
// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onChanged();
}
}
這個mObservers的注冊在setAdapter方法中读恃,調(diào)用的順序依次setAdapter -> setAdapterInternal -> registerAdapterDataObserver -> registerObserver,接下來代碼如下:
public void registerObserver(T observer) {
if (observer == null) {
throw new IllegalArgumentException("The observer is null.");
}
synchronized(mObservers) {
if (mObservers.contains(observer)) {
throw new IllegalStateException("Observer " + observer + " is already registered.");
}
mObservers.add(observer);
}
}
最終可以看出來調(diào)用的是RecyclerView內(nèi)部的成員RecyclerViewDataObserver代态,RecyclerViewDataObserver中的onChanged是最后調(diào)用的目的寺惫,代碼如下:
@Override
public void onChanged() {
assertNotInLayoutOrScroll(null);
mState.mStructureChanged = true;
processDataSetCompletelyChanged(true);
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
最終調(diào)用requestLayout方法,進(jìn)行重新繪制蹦疑。其中processDataSetCompletelyChanged(true)會將所有狀態(tài)進(jìn)行更新肌蜻,保證不進(jìn)行動畫執(zhí)行。
@Override
public void requestLayout() {
if (mEatRequestLayout == 0 && !mLayoutFrozen) {
super.requestLayout();
} else {
mLayoutRequestEaten = true;
}
}
通過super.requestLayout()的調(diào)用會觸發(fā)onLayout()方法必尼,最后調(diào)用dispatchLayout進(jìn)行繪制。
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();
}
以上方法由進(jìn)行dispatchLayoutStep1()篡撵、dispatchLayoutStep2()判莉、dispatchLayoutStep3()方法。其中dispatchLayoutStep3()和動畫繪制和執(zhí)行相關(guān)育谬。
其中dispatchLayoutStep由mState.mLayoutStep的狀態(tài)決定券盅。其中mState.mLayoutStep的狀態(tài)對應(yīng)State.STEP_START、State.STEP_LAYOUT膛檀、State.STEP_ANIMATIONS锰镀。
- 接著看notifyItemChanged局部刷新
public final void notifyItemChanged(int position, @Nullable Object payload) {
mObservable.notifyItemRangeChanged(position, 1, payload);
}
public void notifyItemRangeChanged(int positionStart, int itemCount,
@Nullable Object payload) {
// since onItemRangeChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.
for (int i = mObservers.size() - 1; i >= 0; i--) {
mObservers.get(i).onItemRangeChanged(positionStart, itemCount, payload);
}
}
最終調(diào)用了RecyclerViewDataObserver方法中的onItemRangeChanged方法。
@Override
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
assertNotInLayoutOrScroll(null);
if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
triggerUpdateProcessor();
}
}
void triggerUpdateProcessor() {
if (POST_UPDATES_ON_ANIMATION && mHasFixedSize && mIsAttached) {
ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable);
} else {
mAdapterUpdateDuringMeasure = true;
requestLayout();
}
}
mAdapterHelper.onItemRangeChanged方法將需要執(zhí)行的item加入到AdapterHelper.UpdateO中咖刃,需要執(zhí)行的item的動作(ADD泳炉、REMOVE、UPDATE嚎杨、MOVE)通過AdapterHelper.UpdateOp進(jìn)行記錄花鹅,放入到一個等待隊列中去,并且觸發(fā)requestLayout枫浙,最終經(jīng)過dispatchLayoutStep3()方法執(zhí)行動畫操作刨肃。