RecyclerView源码解析
来源:互联网 发布:windows日志 u盘 编辑:程序博客网 时间:2024/05/17 04:36
关于在项目中需不需要将ListView替换成RecyclerView,这个问题前段时间碰到过,今天就来分析下RecyclerView的实现机制。
1.ListView的实现机制
这块我就不多说,网上关于这块的剖析还是很多的,还是直接贴出郭霖大神的那篇博文吧Android ListView工作原理完全解析,带你从源码的角度彻底理解,如果对于ListView的工作原理感兴趣的,可以搭乘时光机去瞅瞅,这里就不多介绍。
2.RecyclerView工作原理
关于RecyclerView,官方的说法它是ListView的增强版本,它的新特性如ViewHolder、ItemDecorator、LayoutManager、SmothScroller以及增加或删除item时item动画等,而且可以对于listView和GridView进行替代。当在处理复杂布局的情况下,利用RecyclerView会比ListView更加的方便跟灵活。 接下来,我们来分析RecycleView大体的实现机制。在Android中,一个View的绘制大体会经过Measure、Layout、Draw这几个方法,RecyclerView当然也会经历这几个过程。我们首先看看RecyclerView的onMeasure()方法:
@Override protected void onMeasure(int widthSpec, int heightSpec) { ... if (mState.mLayoutStep == State.STEP_START) { dispatchLayoutStep1(); } // set dimensions in 2nd step. Pre-layout should happen with old dimensions for // consistency mLayout.setMeasureSpecs(widthSpec, heightSpec); mState.mIsMeasuring = true; dispatchLayoutStep2(); // now we can get the width and height from the children. mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); // if RecyclerView has non-exact width and height and if there is at least one child // which also has non-exact width & height, we have to re-measure. if (mLayout.shouldMeasureTwice()) { mLayout.setMeasureSpecs(...); mState.mIsMeasuring = true; dispatchLayoutStep2(); // now we can get the width and height from the children. mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); } } else { ... }
我们截取onMeasure()方法中重要的代码进行分析,不难发现,RecyclerView的绘制过程其实是交给了mLayout这个对象,这个对象其实就是我们LayoutManager的实例对象。我们看看主要代码
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
上面onMeasure()方法其实就是做了初始化RecyclerView的属性操作;接着我们看看dispatchLayoutStep1():
private void dispatchLayoutStep1() { ... if (mState.mRunSimpleAnimations) { // Step 0: Find out where all non-removed items are, pre-layout int count = mChildHelper.getChildCount(); for (int i = 0; i < count; ++i) { final ViewHolder holder = ... ... final ItemHolderInfo animationInfo = mItemAnimator .recordPreLayoutInformation(...); mViewInfoStore.addToPreLayout(holder, animationInfo); ... } } ...}
截取了部分代码内容,这个方法做的操作其实就是Pre-Layout,我们关注三个地方,第一,ItemHolderInfo:这个对象保存了RecycleView中的一些边界信息。
public ItemHolderInfo setFrom(RecyclerView.ViewHolder holder, @AdapterChanges int flags) { final View view = holder.itemView; this.left = view.getLeft(); this.top = view.getTop(); this.right = view.getRight(); this.bottom = view.getBottom(); return this; }
第二,mViewInfoStore,这个对象保存的是RecyclerView中item的动画信息。
/** * Keeps data about views to be used for animations */ final ViewInfoStore mViewInfoStore = new ViewInfoStore();
第三,addToPreLayout(),这个方法主要就是将pre-layout阶段ItemView的信息保存在mViewInfoStore中的mLayoutHolderMap中,做的是一些保存信息操作。
/** * Adds the item information to the prelayout tracking * @param holder The ViewHolder whose information is being saved * @param info The information to save */ void addToPreLayout(ViewHolder holder, ItemHolderInfo info) { InfoRecord record = mLayoutHolderMap.get(holder); if (record == null) { record = InfoRecord.obtain(); mLayoutHolderMap.put(holder, record); } record.preInfo = info; record.flags |= FLAG_PRE; }
接着,回到onMeasure()方法,继续跟进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 ... mLayout.onLayoutChildren(mRecycler, mState); ... }
从源码的注释我们能够看出,这一步的操作就是布局子控件。 mLayout.onLayoutChildren(mRecycler, mState);
进入该方法我们发现,这个方法是个空方法,必须要被重写。也就是说,在实现RecyclerView.LayoutManager这个抽象类的时候,需要重写onLayoutChildren()这个方法,由于在RecyclerView中,提供了3个RecyclerView.LayoutManager的实现类,我们就以LinearLayoutManager类中的onLayoutChildren()方法来进行说明(similar functionality to {@link android.widget.ListView})。
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 ... ensureLayoutState(); ... if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION || mPendingSavedState != null) { mAnchorInfo.reset(); mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd; // calculate anchor position and coordinate updateAnchorInfoForLayout(recycler, state, mAnchorInfo); mAnchorInfo.mValid = true; } ... if (mAnchorInfo.mLayoutFromEnd) { ... } else { ... } onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection); detachAndScrapAttachedViews(recycler); ... if (mAnchorInfo.mLayoutFromEnd) { // fill towards start updateLayoutStateToFillStart(mAnchorInfo); mLayoutState.mExtra = extraForStart; fill(recycler, mLayoutState, state, false); startOffset = mLayoutState.mOffset; ... // fill towards end updateLayoutStateToFillEnd(mAnchorInfo); mLayoutState.mExtra = extraForEnd; mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; fill(recycler, mLayoutState, state, false); endOffset = mLayoutState.mOffset; ... } else { // fill towards end updateLayoutStateToFillEnd(mAnchorInfo); mLayoutState.mExtra = extraForEnd; fill(recycler, mLayoutState, state, false); endOffset = mLayoutState.mOffset; final int lastElement = mLayoutState.mCurrentPosition; if (mLayoutState.mAvailable > 0) { extraForStart += mLayoutState.mAvailable; } // fill towards start updateLayoutStateToFillStart(mAnchorInfo); mLayoutState.mExtra = extraForStart; mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; fill(recycler, mLayoutState, state, false); startOffset = mLayoutState.mOffset; if (mLayoutState.mAvailable > 0) { extraForEnd = mLayoutState.mAvailable; // start could not consume all it should. add more items towards end updateLayoutStateToFillEnd(lastElement, endOffset); mLayoutState.mExtra = extraForEnd; fill(recycler, mLayoutState, state, false); endOffset = mLayoutState.mOffset; } } ... }
我们可以从注释中了解到,onLayoutChidren()这个方法主要是通过获取一个anchor (锚点)的信息来以此为起始方向或者结束方向来填充itemView。在上述方法中,真正执行填充的方法不难发现是fill()这个方法。我们跟进fill()这个方法:
/**The magic functions :). Fills the given layout, defined by the layoutState. This is fairly independent from the rest of the {@link android.support.v7.widget.LinearLayoutManager} and with little change, can be made publicly available as a helper class. */ int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { ... int remainingSpace = layoutState.mAvailable + layoutState.mExtra; LayoutChunkResult layoutChunkResult = new LayoutChunkResult(); while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) { layoutChunkResult.resetInternal(); layoutChunk(recycler, state, layoutState, layoutChunkResult); ... if (!layoutChunkResult.mIgnoreConsumed || mLayoutState.mScrapList != null || !state.isPreLayout()) { layoutState.mAvailable -= layoutChunkResult.mConsumed; // we keep a separate remaining space because mAvailable is important for recycling remainingSpace -= layoutChunkResult.mConsumed; } if (layoutState.mScrollingOffset != LayoutState.SCOLLING_OFFSET_NaN) { layoutState.mScrollingOffset += layoutChunkResult.mConsumed; if (layoutState.mAvailable < 0) { layoutState.mScrollingOffset += layoutState.mAvailable; } recycleByLayoutState(recycler, layoutState); } ... return start - layoutState.mAvailable; }
在上述方法中,有一个while()循环,其中有一个layoutChunk()内部方法,我们继续看一下:
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); 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); ... }
在这个方法中,所做的事情就是添加子view(addView())与测量子view的大小(measureChildWithMargins())。我们看看最后调用的一个方法,layoutDecoratedWithMargins():
/** Lay out the given child view within the RecyclerView using coordinates that include any current {@link ItemDecoration ItemDecorations} and margins. */ public void layoutDecoratedWithMargins(View child, int left, int top, int right,int bottom) { ... child.layout(...); }
这个方法就是布局子view的方法。接下来我们梳理下RecyclerView填充ItemView的算法:1.向父容器中添加子控件;2.测量子控件的大小;3.将测量后的子控件进行布局,将锚点向布局的方向进行平移,直到RecyclerView的子控件全部填充或者其绘制空间已绘制完毕。RecyclerView的绘制空间就是RecyclerView的父容器规定RecyclerView所占的空间大小。
接下来就是来看看RecyclerView的draw()方法部分,同所有的view的绘制一样,RecyclerView在这个方法将子控件一一的绘制出来。
@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); } ... } @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); } }
我们发现,draw()方法十分的简单,就是确定子View距离边界的offset,然后将其画出来。
RecyclerView的滑动机制
了解RecyclerView的滑动机制首先我们来看看其onTouchEvent()这个方法:
@Override public boolean onTouchEvent(MotionEvent e) { ... if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } boolean eventAddedToVelocityTracker = false; final MotionEvent vtev = MotionEvent.obtain(e); final int action = MotionEventCompat.getActionMasked(e); final int actionIndex = MotionEventCompat.getActionIndex(e); ... switch (action) { case MotionEvent.ACTION_DOWN: { mScrollPointerId = MotionEventCompat.getPointerId(e, 0); mInitialTouchX = mLastTouchX = (int) (e.getX() + 0.5f); mInitialTouchY = mLastTouchY = (int) (e.getY() + 0.5f); int nestedScrollAxis = ViewCompat.SCROLL_AXIS_NONE; if (canScrollHorizontally) { nestedScrollAxis |= ViewCompat.SCROLL_AXIS_HORIZONTAL; } if (canScrollVertically) { nestedScrollAxis |= ViewCompat.SCROLL_AXIS_VERTICAL; } startNestedScroll(nestedScrollAxis); } break; case MotionEventCompat.ACTION_POINTER_DOWN: { ... case MotionEvent.ACTION_MOVE: { final int index = MotionEventCompat.findPointerIndex(e, mScrollPointerId); ... final int x = (int) (MotionEventCompat.getX(e, index) + 0.5f); final int y = (int) (MotionEventCompat.getY(e, index) + 0.5f); int dx = mLastTouchX - x; int dy = mLastTouchY - y; ... if (mScrollState != SCROLL_STATE_DRAGGING) { boolean startScroll = false; if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { if (dx > 0) { dx -= mTouchSlop; } else { dx += mTouchSlop; } startScroll = true; } if (canScrollVertically && Math.abs(dy) > mTouchSlop) { if (dy > 0) { dy -= mTouchSlop; } else { dy += mTouchSlop; } startScroll = true; } if (startScroll) { setScrollState(SCROLL_STATE_DRAGGING); } } 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); } } } break; ... case MotionEvent.ACTION_UP: { mVelocityTracker.addMovement(vtev); eventAddedToVelocityTracker = true; mVelocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity); final float xvel = canScrollHorizontally ? -VelocityTrackerCompat.getXVelocity(mVelocityTracker, mScrollPointerId) : 0; final float yvel = canScrollVertically ? -VelocityTrackerCompat.getYVelocity(mVelocityTracker, mScrollPointerId) : 0; if (!((xvel != 0 || yvel != 0) && fling((int) xvel, (int) yvel))) { setScrollState(SCROLL_STATE_IDLE); } resetTouch(); } break; ... } ... }
RecyclerView的滑动机制主要的思想为:首先接触到ACTION_MOVE事件之后,会计算出手指在屏幕上的移动距离(dx、dy),然后根据该移动的距离与mTouchSlop(阈值)进行对比,从而设置RecyclerView的滑动状态(setScrollState(SCROLL_STATE_DRAGGING);),最终,通过调用scrollByInternal()这个方法实现滑动。这个时候的滑动并没有结束,继续接受ACTION_UP事件之后,RecyclerView会通过VelocityTracker来计算其滑动的一个初始速度(xvel、yvel)当拿到这个速度值之后,调用fling()方法实现后续的滑动,最后初始化RecyclerView的touch事件。总结下说,就是RecyclerView的滑动主要涉及到了两个核心的方法,scrollByInternal()与fling()。
1.scrollByInternal()
看看源码:
boolean scrollByInternal(int x, int y, MotionEvent ev) { ... if (mAdapter != null) { eatRequestLayout(); onEnterLayoutOrScroll(); TraceCompat.beginSection(TRACE_SCROLL_TAG); if (x != 0) { consumedX = mLayout.scrollHorizontallyBy(x, mRecycler, mState); unconsumedX = x - consumedX; } if (y != 0) { consumedY = mLayout.scrollVerticallyBy(y, mRecycler, mState); unconsumedY = y - consumedY; } TraceCompat.endSection(); repositionShadowingViews(); onExitLayoutOrScroll(); resumeRequestLayout(false); } ... if (dispatchNestedScroll(consumedX, consumedY, unconsumedX, unconsumedY, mScrollOffset)) { // Update the last touch co-ords, taking any scroll offset into account mLastTouchX -= mScrollOffset[0]; mLastTouchY -= mScrollOffset[1]; if (ev != null) { ev.offsetLocation(mScrollOffset[0], mScrollOffset[1]); } mNestedOffsets[0] += mScrollOffset[0]; mNestedOffsets[1] += mScrollOffset[1]; } else if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) { if (ev != null) { pullGlows(ev.getX(), unconsumedX, ev.getY(), unconsumedY); } considerReleasingGlowsOnScroll(x, y); } ... }
从上述方法中,我们可以看到该方法兜兜转转,最后还是会调用到LinearLayoutManager中的ScrollBy()方法。这里就不在赘述。
2.fling()
老样子,还是先看源码:
public boolean fling(int velocityX, int velocityY) { ... if (canScroll) { velocityX = Math.max(-mMaxFlingVelocity, Math.min(velocityX, mMaxFlingVelocity)); velocityY = Math.max(-mMaxFlingVelocity, Math.min(velocityY, mMaxFlingVelocity)); mViewFlinger.fling(velocityX, velocityY); return true; } } return false; }
fling()方法中核心的代码就是mViewFlinger.fling(velocityX, velocityY);这句。mViewFlinger是一个runnable对象,而RecyclerView中的fling()还是通过Scroller来实现的。继续深入:
public ViewFlinger() { mScroller = ScrollerCompat.create(getContext(), sQuinticInterpolator); }//mViewFlinger.fling(...) public void fling(int velocityX, int velocityY) { setScrollState(SCROLL_STATE_SETTLING); mLastFlingX = mLastFlingY = 0; mScroller.fling(0, 0, velocityX, velocityY, Integer.MIN_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MAX_VALUE); postOnAnimation(); }
继续看看mViewFlinger中的run()方法,看看里面到底做了什么事情:
public void run() { ... if (scroller.computeScrollOffset()) { final int x = scroller.getCurrX(); final int y = scroller.getCurrY(); final int dx = x - mLastFlingX; final int dy = y - mLastFlingY; ... if (mAdapter != null) { eatRequestLayout(); onEnterLayoutOrScroll(); TraceCompat.beginSection(TRACE_SCROLL_TAG); if (dx != 0) { hresult = mLayout.scrollHorizontallyBy(dx, mRecycler, mState); overscrollX = dx - hresult; } if (dy != 0) { vresult = mLayout.scrollVerticallyBy(dy, mRecycler, mState); overscrollY = dy - vresult; } TraceCompat.endSection(); repositionShadowingViews(); onExitLayoutOrScroll(); resumeRequestLayout(false); ... if (!awakenScrollBars()) { invalidate(); } ... if (scroller.isFinished() || !fullyConsumedAny) { setScrollState(SCROLL_STATE_IDLE); // setting state to idle will stop this. } else { postOnAnimation(); } } ... }
这段代码中需要注意一个方法mLayout.scrollHorizontallyBy(dx, mRecycler, mState);这个方法最终会走到LinearLayoutManager中的ScrollBy()方法。上段代码的思想就是根据Scroller事件偏移量来计算itemView的平移距离或者是在此创建itemView。
最后我们来看看LinearLayoutManager中的ScrollBy()方法:
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { ... final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START; final int absDy = Math.abs(dy); updateLayoutState(layoutDirection, absDy, true, state); final int consumed = mLayoutState.mScrollingOffset + fill(recycler, mLayoutState, state, false); ... final int scrolled = absDy > consumed ? layoutDirection * consumed : dy; mOrientationHelper.offsetChildren(-scrolled); .... }
在这个方法中,我们可以看到RecyclerView填充itemView的绘制区域就是滑动事情的偏移量,而 mOrientationHelper.offsetChildren(-scrolled);这个方法就是平移itemView的方法。所以RecyclerView的滑动最终都是通过ScrollBy()这个方法进行操作的。
3.RecyclerView的复用机制
首先,我们回顾一下在前面的onLayoutChildren()这个方法中,使用的 fill(recycler, mLayoutState, state, false)方法来填充itemView,跟进之后我们发现,其实最终是调用RecyclerView.Recycler.getViewForPosition()方法。
View getViewForPosition(int position, boolean dryRun) { ... boolean fromScrap = false; 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) { ... // 2) Find from scrap via stable ids, if exists if (mAdapter.hasStableIds()) { holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); ... } 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 // try recycler. // Head to the shared pool. ... 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()) { ... mAdapter.bindViewHolder(holder, offsetPosition); ... } ... }
getViewForPosition()这个方法比较长,但是不难理解,如果您对listView的复用机制有过了解,那么这里理解起来就相对的容易一些。梳理下这个方法的思路:这个方法是根据列表位置获取ItemView,先后从scrapped、cached、exCached、recycled集合中查找相对应的ItemView,最后没有找到的话,就新建一个新的holder( holder = mAdapter.createViewHolder(RecyclerView.this, type))最后与数据集进行绑定显示。scrapped、cached、exCached集合是定义在RecyclerView.Recycler中,scrapped表示废弃的ItemView,这里可以理解为删除掉的ItemView,cached与exCached分别表示一级和二级缓存的ItemView。recycled集合就是一个map,它是定义在RecyclerView.RecyclerViewPool中,它的作用就是将ItemView以ItemType分类保存起来。
好了,讲述了RecyclerView复用的ItmeView是从哪里来的,那么什么时候它们被添加进了复用的集合中呢?scrapped集合中的ItemView是当我们执行remove操作释放的ItemView,而caced集合中的ItemView呢?答案当然是在前面布局的时候,还记得在布局onLayoutChildren()方法中的核心方法fill()时,当我们循环的向RecyclerView的可绘制区域添加ItmeView的时候,其实最后执行了 recycleByLayoutState(recycler, layoutState)这个方法,这个方法我们最终可以追溯到RccyclerView.Recyler.recycleViewHolderInternal()方法:
/** * internal implementation checks if view is scrapped or attached and throws an exception */ void recycleViewHolderInternal(ViewHolder holder) { ... if (forceRecycle || holder.isRecyclable()) { if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_UPDATE)) { // Retire oldest cached view int cachedViewSize = mCachedViews.size(); if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) { recycleCachedViewAt(0); cachedViewSize --; } if (cachedViewSize < mViewCacheMax) { mCachedViews.add(holder); cached = true; } } if (!cached) { addViewHolderToRecycledViewPool(holder); recycled = true; } } ... }
从源码中我们可以发现,其实在RecyclerView的layout阶段就将ItmeView存入了cached集合,上述代码的逻辑思路为,判断cached集合是否存满,若存满了,则将ItmeView移除到recycled集合中,若没有存满,则添加ItemView到cached集合中。
RecyclerView动画机制
RecyclerView的动画其实也是跟数据集与ItemView绑定在一起的,在RecyclerView中定义了4种对数据集的操作,分别为Add、Remove、Update、Move。这4个数据集的操作封装在AdapterHelper.UpdateOp类中,而且所有的操作由一个大小为30的对象池进行管理,当我们执行任何一个操纵的时候,都会从对象池中取出一个UpdateOp对象,最后调用RecyclerView.RecyclerViewDataObserver.triggerUpdateProcessor()方法,这个方法会跟据等待队列中的信息,对所有的子控件进行重新测量、布局、重绘且执行动画。
我们举个栗子,当我们删除某个ItemView的时候,我们会调用Adapter.notifyItemRemove()这个方法。最终会追溯到RecyclerView.RecyclerViewDataObserver.onItemRangeRemove():
@Override public void onItemRangeRemoved(int positionStart, int itemCount) { ... if(mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) { triggerUpdateProcessor(); } }
通过上面源码可以发现,在执行onItemRangeRemoved()这个方法时,会调用triggerUpdateProcessor()这个方法,我们再进入triggerUpdateProcessor()这个方法看看:
void triggerUpdateProcessor() { if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) { ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable); } else { mAdapterUpdateDuringMeasure = true; requestLayout(); } }
在这个方法里面会调用requestLayout()这个方法,那么接下来的操作就跟RecyclerView的绘制流程一致了,还有个问题,动画是在什么时候执行的?
回想下我们在布局的时候,关注过dispatchLayoutStep2这个方法,其实动画的执行是在dispatchLayoutStep3中执行的。看源码:
/** * The final step of the layout where we save the information about views for animations, * trigger animations and do any necessary cleanup. */ private void dispatchLayoutStep3() { ... if (mState.mRunSimpleAnimations) { // Step 3: Find out where things are now, and process change animations. // traverse list in reverse because we may call animateChange in the loop which may // remove the target view holder. for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) { ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i)); ... // Step 4: Process view info lists and trigger animations mViewInfoStore.process(mViewInfoProcessCallback); } ... }
我们关注下step4,这里注释上明确说明触发动画的地方。我们看看process()这个方法的源码:
void process(ProcessCallback callback) { for (int index = mLayoutHolderMap.size() - 1; index >= 0; index --) { final ViewHolder viewHolder = mLayoutHolderMap.keyAt(index); final InfoRecord record = mLayoutHolderMap.removeAt(index); ... } }
我们注意下传入的ProcessCallback 这个对象:
/** * The callback to convert view info diffs into animations. */ private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback = new ViewInfoStore.ProcessCallback() { @Override public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info, @Nullable ItemHolderInfo postInfo) { mRecycler.unscrapView(viewHolder); animateDisappearance(viewHolder, info, postInfo); } @Override public void processAppeared(ViewHolder viewHolder, ItemHolderInfo preInfo, ItemHolderInfo info) { animateAppearance(viewHolder, preInfo, info); } ... };
注意animateDisappearance(),这里就是我们执行动画的方法所在,我们再跟进查看下:
private void animateDisappearance(@NonNull ViewHolder holder, @NonNull ItemHolderInfo preLayoutInfo, @Nullable ItemHolderInfo postLayoutInfo) { **addAnimatingView(holder);** holder.setIsRecyclable(false); if (mItemAnimator.animateDisappearance(holder, preLayoutInfo, postLayoutInfo)) { *postAnimationRunner();* } }
首先,说明下addAnimatingView()这个方法,这个方法最终会向mHiddenViews这个集合中添加ItemView,而所添加的ItemView,最终会以getViewForPosition()方法中的getScrapViewForPosition()来获取。接着我们说下animateDisappearance()这个方法,它其实是将动画与ItmeView进行绑定,并且添加到一个等待的执行的队列中,而当postAnimationRunner()这个方法被调用的时候,就会执行这个队列中的动画,这样就实现了RecyclerView的操作数据集的动画效果。
参考
http://blog.csdn.net/qq_23012315/article/details/50807224
- RecyclerView源码解析
- RecyclerView源码解析
- Android中RecyclerView源码解析
- RecyclerView 使用及源码解析
- RecyclerView.ItemAnimator终极解读(一)--RecyclerView源码解析
- 从源码解析RecyclerView绘制流程
- RecyclerView源码解析之缓存机制
- RecyclerView.ItemAnimator终极解读(二)--SimpleItemAnimator和DefaultItemAnimator源码解析
- 最全面的RecyclerView源码解析(一)
- RecyclerView 解析
- RecyclerView 解析
- RecyclerView 解析
- RecyclerView解析
- RecyclerView解析
- RecyclerView解析
- RecyclerView源码分析
- RecyclerView 的源码浅析
- RecyclerView源码分析
- Python中遇到"UnicodeDecodeError: ‘gbk’ codec can’t decode bytes in position 2-3: illegal multibyte sequ
- Python大数据处理模块Pandas
- 开发中遇到的Error汇总
- 虚函数
- POJ2106 Boolean Expressions
- RecyclerView源码解析
- 面试题-华为(16年)-字符集合去重-5
- ADS仿真目标参数调谐与目标优化 ----tuner和goal控件使用
- 关于Integer大小比较的问题
- palette调色板初级使用
- 链表的学习 day1
- 继承、封装、多态
- 03-S3C2440u-boot学习之u-boot分析之u-boot命令实现
- 关于localtime()函数在多线程中的core问题