上一篇介绍基本使用,这一篇我们深入分析RecyclerView内部被渲染的流程,在这里我们从三个方面来了解流程,具体三个方面如下:
- 启动渲染:一般首次渲染的时候。
- 滑动渲染: 手指触摸屏幕滑动直到离开的过程。
- 通知渲染:一般调用
notifyDataSetChanged() 或者 notifyItemChanged()
时候。
1、启动渲染
RecyclerView继承ViewGroup,同样准守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{
//...省略
}
}
我们只看核心代码,现在我们以LinearLayoutManager为例子,LinearLayoutManager重写了isAutoMeasureEnabled()方法并且返回true,这样确定了RecyclerView的绘制走了自动布局的逻辑。如果Recyclerview确定了画布大小(宽高都是MeasureSpec.EXACTLY)直接return,测量结束。重点还是dispatchLayoutStep1()、dispatchLayoutStep2()逻辑。其中dispatchLayoutStep1()的代码如下:
/**
* The first step of a layout where we;
* - process adapter updates 处理adapter更新
* - decide which animation should run 决定哪个执行动画
* - save information about current views 保存当前view的信息
* - If necessary, run predictive layout and save its information 是否进行预加载并且保存预加载信息
*/
private void dispatchLayoutStep1() {
mState.mIsMeasuring = false;
processAdapterUpdatesAndSetAnimationFlags();
saveFocusInfo();
//...省略
if (mState.mRunSimpleAnimations) {
//...省略
}
if (mState.mRunPredictiveAnimations) {
//...省略
}
// ..省略
mState.mLayoutStep = State.STEP_LAYOUT;
}
dispatchLayoutStep1()主要还是更新状态(mState)和视图(mViewInfoStore)为下一步绘制做准备。接下来看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()实现了RecyclerView的绝大部分视图渲染逻辑。我们这边以LinearLayoutMananger为例子进行绘制分析,接下来看看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
//以上注释已经说明了绘制算法:1、先确定锚点(anchor)2、布局方向由下往上或者由上往下绘制。
//...省略
if (!mAnchorInfo.mValid || mPendingScrollPosition != RecyclerView.NO_POSITION
|| mPendingSavedState != null) { //首次绘制确定锚点、满足次条件。
mAnchorInfo.reset();
//确定绘制步骤,决定由上往下还是由下往上绘制。
mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
// calculate anchor position and coordinate 计算anchor的position 和 coordinate
//计算anchor由一套规则,anchor依次由滑动mPendingSavedState、聚焦view来确定,如果未发现初始化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聚焦视图情况下,满足条件更新锚点信息。
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);
}
//...省略
}
以上代码体现锚点的计算非常重要,我们来看一下锚点(AnchorInfo)和布局状态(LayoutState)重要属性。
/**
* Simple data class to keep Anchor information
*/
static class AnchorInfo {
OrientationHelper mOrientationHelper; //计算start,end等辅助类
int mPosition; //Anchor 启点的位置
int mCoordinate; //Anchor Y轴上起始绘制偏移量
boolean mLayoutFromEnd; //布局方向
boolean mValid; //锚点是否合法
}
/**
* 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.
* 当前位置
*/
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.
* 提前预加载空间
* 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;
}
了解重要属性之后,下面我们通过图片来表达核心的绘制逻辑。
图一 图二
图一为首次RecyclerView绘制的时候,AnchorInfo{mPosition = 0,mCoordinate = 0} ,mLayoutDirection = LayoutState.LAYOUT_START 时候往上绘制空间为0,所以只考虑往下绘制(mAvailable + mExtraFillSpace)。图二为多数滑动或者聚焦view(比如resume界面)的时候渲染经过。在onLayoutChildren方法中调用updateLayoutStateToFillStart或者updateLayoutStateToFillEnd将计算结果更新至LayoutState,最后交付给fill进行填充绘制。
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剩余高度,当remainingSpace需要填充的时候调用layoutChunk()方法进行item绘制,并且不断更新对应mConsumed,接下来我们看下layoutChunk方法。
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
View view = layoutState.next(recycler); //这个方法非常重要,直接包含了recyclerview的缓存机制,此处暂时没进行解释,缓存讲解的时候再来看这个方法。
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 {
//此处暂时不看,这边预加载动画相关的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调整到最终的位置。到此处我们来看一下item的包装类,补充下OrientationHelper,这个类协助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,经过ACTION_MOVE,计算dy或者dx,并且mTouchSlop为滑动阀值(这个值可初始化时候决定,能决定滑动偏移值),dy或者dx大于这个值触发scrollByInternal方法,我们来看一下scrollByInternal方法。
boolean scrollByInternal(int x, int y, MotionEvent ev, int type) {
//...省略
if (mAdapter != null) {
//...省略
scrollStep(x, y, mReusableIntPair);
//...省略
}
//...省略
}
在onTouchEvent中,当ACTION_UP手指抬起来的时候,经过VelocityTrackerCompat根据初速度来计算是否触发fling()事件,我们先来看看fling的调用过程。
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);
}
最终调用的是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);
}
//...省略
}
最终调用了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方法中,调用的顺序依次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);
}
}
最终可以看出来调用的是RecyclerView内部的成员RecyclerViewDataObserver,RecyclerViewDataObserver中的onChanged是最后调用的目的,代码如下:
@Override
public void onChanged() {
assertNotInLayoutOrScroll(null);
mState.mStructureChanged = true;
processDataSetCompletelyChanged(true);
if (!mAdapterHelper.hasPendingUpdates()) {
requestLayout();
}
}
最终调用requestLayout方法,进行重新绘制。其中processDataSetCompletelyChanged(true)会将所有状态进行更新,保证不进行动画执行。
@Override
public void requestLayout() {
if (mEatRequestLayout == 0 && !mLayoutFrozen) {
super.requestLayout();
} else {
mLayoutRequestEaten = true;
}
}
通过super.requestLayout()的调用会触发onLayout()方法,最后调用dispatchLayout进行绘制。
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();
}
以上方法由进行dispatchLayoutStep1()、dispatchLayoutStep2()、dispatchLayoutStep3()方法。其中dispatchLayoutStep3()和动画绘制和执行相关。
其中dispatchLayoutStep由mState.mLayoutStep的状态决定。其中mState.mLayoutStep的状态对应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);
}
}
最终调用了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方法将需要执行的item加入到AdapterHelper.UpdateO中,需要执行的item的动作(ADD、REMOVE、UPDATE、MOVE)通过AdapterHelper.UpdateOp进行记录,放入到一个等待队列中去,并且触发requestLayout,最终经过dispatchLayoutStep3()方法执行动画操作。
网友评论