安卓5.1源码解析 : RecyclerView解析 从绘制流程,ViewHolder复用机制,LayoutManger,ItemAnimator等流程全面讲解

最近一直在研究 安卓中几个常用控件的源码,希望能通过学习源码学习到google大牛在封装一些复杂view的思想,为以后自己造轮子提供更好的思路.
RecyclerView是一个用户可以全面定制的组件,本文将全面分析RecyclerView的各种机制,包括 viewholder复用机制, LayoutManager布局机制, ItemAnimatoritem动画等 RecyclerView暴露给使用者的所有可以自定义的部分在源码中的体现. RecylerView完全区别于 ListView,尤其在Item的复用方面, RecyclerView不在让用户关注于Item的复用,让用户可以更专注去处理UI上的逻辑.
本文将从以下几个方面对RecyclerView进行讲解
  • onMeasure
  • onLayout
  • item测量
  • item布局
  • ItemDecoraction
  • RecyclerView的滑动以及ViewHolder复用机制
  • RecyclerView动画
注 : 其中会穿插着对 LayoutManager, ItemAnimator等用户自定义组件的分析.
onMeasure
  • RecyclerView的onMeasure方法
@OverrideprotectedvoidonMeasure(int widthSpec, int heightSpec) { ... if (mLayout == null) { defaultOnMeasure(widthSpec, heightSpec); } else { //调用LayoutManager中的方法测量view mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); } mState.mInPreLayout = false; // clear }
可以看到 ReyelcerViewonMeasure这里有个判断 如果 mLayout不为空的时候,会调用 mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);进行测量,这个 mLayout其实就是 LayoutManager,负责了RecyelcerView的测量.一般来说如果我们想自定义 ReyclerViewonMeasure方法,只要在setLayoutManager方法中放入自己的自定义LayoutManger就可以了,系统为我们实现了 LinearLayoutManager用来摆放,而这个类里面并没有重写LayoutManager的 onMeasure方法,所以我们直接查看 LayoutManaer默认的测量方法看看.
下面我们来通过分析 LayoutManager看一下它是怎么进行对onMeasure的处理
  • LinearLayoutManager的onMeasure
publicvoidonMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) { mRecyclerView.defaultOnMeasure(widthSpec, heightSpec); }
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
它这里又调用了recyclerView的 defaultOnMeasure(widthSpec, heightSpec);默认的measure方法
  • mRecyclerView.defaultOnMeasure(widthSpec, heightSpec)
privatevoiddefaultOnMeasure(int widthSpec, int heightSpec) { finalint widthMode = MeasureSpec.getMode(widthSpec); finalint heightMode = MeasureSpec.getMode(heightSpec); finalint widthSize = MeasureSpec.getSize(widthSpec); finalint heightSize = MeasureSpec.getSize(heightSpec); int width = 0; int height = 0; switch (widthMode) { case MeasureSpec.EXACTLY: case MeasureSpec.AT_MOST: width = widthSize; break; case MeasureSpec.UNSPECIFIED: default: width = ViewCompat.getMinimumWidth(this); break; } switch (heightMode) { case MeasureSpec.EXACTLY: case MeasureSpec.AT_MOST: height = heightSize; break; case MeasureSpec.UNSPECIFIED: default: height = ViewCompat.getMinimumHeight(this); break; } setMeasuredDimension(width, height); }
  • 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
  • 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
很熟悉的代码,主要就是对 RecyclerView根据测量模式进行测量,最后通过 setMeasuredDimension(width, height);给成员变量 measuredWidthmeasuredHeight赋值.可是这时候就会有疑惑,这里并没有看到对子view的测量, ListView在这里就会对子view进行测量了,为什么RecyclerView没有,难道是我们分析错了吗?我们接着往下看…..
onLayout
我们看下 ReyclerView的onLayout方法
@OverrideprotectedvoidonLayout(boolean changed, int l, int t, int r, int b) { eatRequestLayout(); //分发Layout事件 dispatchLayout(); resumeRequestLayout(false); mFirstLayoutComplete = true; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
这里有个dispatchLayout方法,根据方法名我们可以猜到应该是给子控件分发layout事件的方法,我们点进去看看
  • dispatchLayout(); 进行布局的方法
void dispatchLayout() { //第一次onLayout的时候mAdapter 和 mLayout肯定为空,所以不会有下面的逻辑,只有当我们调用setAdapter,或者其他第二次//重绘的方法,才会继续下面的逻辑if (mAdapter == null) { Log.e(TAG, "No adapter attached; skipping layout"); return; } if (mLayout == null) { Log.e(TAG, "No layout manager attached; skipping layout"); return; } //这里通过我们传入的Adapter的getItemCount方法拿到了Item的个数 mState.mItemCount = mAdapter.getItemCount(); ....进行些布局前的初始化操作... // Step 2: Run layout//开始layout mState.mInPreLayout = false; //具体怎么布局,会调用LayoutManager里面的方法进行布局 mLayout.onLayoutChildren(mRecycler, mState); ....后面是对ItemAnimator动画的执行,我们后面再讲解... }
  • 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
  • 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
可以看出 dispatchLayout()最后还是会调用 mLayout.onLayoutChildren(mRecycler, mState);,我们上面说过 mLayout就是我们传递入的 LayoutManager,调用 LayoutManageronLayoutChildren进行布局,我们去看看 LinearLayoutManager的onLayoutChildren是如何进行布局的.
  • LinearLayoutManager.onLayoutChildren
关于布局锚点: onLayoutChildren中会先确认布局锚点mAnchor,然后从布局锚点为开始位置,以此为起点向开始和结束方向填充ItemView.
mAnchor包含了子控件在Y轴上起始绘制偏移量(coordinate),ItemView在Adapter中的索引位置(position)和布局方向
@OverridepublicvoidonLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { // layout algorithm: 布局算法// 1) by checking children and other variables, find an anchor coordinate and an anchor// item position. //通过检查孩子和其他变量,找到锚坐标和锚点项目位置 mAnchor为布局锚点 //mAnchor包含了子控件在Y轴上起始绘制偏移量(coordinate),ItemView在Adapter中的索引位置(position)和布局方向(mLayoutFromEnd)// 2) fill towards start, stacking from bottom , 开始填充, 从底部堆叠 从开始位置开始,向结束为位置堆叠填充itemView// 3) fill towards end, stacking from top 结束填充,从顶部堆叠// 4) scroll to fulfill requirements like stack from bottom. //滚动以满足堆栈从底部的要求// create layout state ... //这个方法会根据LinearLayoutManger构造中传入的布局方向给mShouldReverseLayout赋值//如果是竖直方向(VERTICAL),mShouldReverseLayout为false resolveShouldLayoutReverse(); //重置mAnchorInfo mAnchorInfo.reset(); //得到堆叠方向 mShouldReverseLayout为false mStackFromEnd默认为false//我们假定传入的方向是垂直方向,所以mAnchorInfo.mLayoutFromEnd = false mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd; ... //重要的堆叠Item的方法,根据堆叠方向进行堆叠//如果是end方向 从底部开始堆叠if (mAnchorInfo.mLayoutFromEnd) { // fill towards start // 开始填充 updateLayoutStateToFillStart(mAnchorInfo); mLayoutState.mExtra = extraForStart; fill(recycler, mLayoutState, state, false); startOffset = mLayoutState.mOffset; if (mLayoutState.mAvailable > 0) { extraForEnd += mLayoutState.mAvailable; } // fill towards end //结束填充 updateLayoutStateToFillEnd(mAnchorInfo); mLayoutState.mExtra = extraForEnd; mLayoutState.mCurrentPosition += mLayoutState.mItemDirection; fill(recycler, mLayoutState, state, false); endOffset = mLayoutState.mOffset; } else { //如果排列方向是VERTICAL,走这里// fill towards end //结束填充 updateLayoutStateToFillEnd(mAnchorInfo); mLayoutState.mExtra = extraForEnd; //重要的填充方法 fill(recycler, mLayoutState, state, false); endOffset = mLayoutState.mOffset; 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; }
  • 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
  • 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
上面的代码,会计算 mAnchorInfo.mLayoutFromEnd的值,这个值是通过 mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;进行计算的, mShouldReverseLayout的值在 resolveShouldLayoutReverse();中获取,其中会根据布局朝向去给 mShouldReverseLayout赋值,如果布局朝向是VERTICAL,就为false,反之true. mStackFromEnd是通过 public void setStackFromEnd(boolean stackFromEnd)方法进行赋值,这个方法需要调用者调用,我们一般不调用,所以为初始值false.所以根据或运算,如果是竖直方向mAnchorInfo.mLayoutFromEnd为false.
得到了布局方向,就会调用相应的逻辑进行布局,最后填充的方法为 fill.

Item测量,Item布局
  • fill
//具体的填充方法int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { ... while (remainingSpace > 0 && layoutState.hasMore(state)) { layoutChunkResult.resetInternal(); //填充核心方法,从复用集合中取ViewHolder layoutChunk(recycler, state, layoutState, layoutChunkResult); ... //向复用集合中存ViewHolder recycleByLayoutState(recycler, layoutState); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
fill方法中核心的填充方法 layoutChunk,他会先从缓存中取ViewHolder,如果没有,就回去创建,之后会将创建好的ViewHolder放入复用集合中.我们先看 layoutChunk如何填充的
  • layoutChunk
这个方法就是核心的布局方法, layoutState.next(recycler);是从缓存机制从取Item的具体方法,这个我们下面会说到.
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { //获取ItemView,会先从Scrap中取,如果没有会从一级缓存,二级缓存取,最后检查ReyclerViewPool 如果都没有 就创建ViewHolder View view = layoutState.next(recycler); if (view == null) { if (DEBUG && layoutState.mScrapList == null) { thrownew RuntimeException("received null view when unexpected"); } // if we are laying out views in scrap, this may return null which means there is// no more items to layout. result.mFinished = true; return; } RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) view.getLayoutParams(); //如果mScrapList为空,就将view填充进去,这个mScrapList就是Item被移除屏幕被缓存起来的集合,如果没有在mScrapList中//说明需要添加到RecyerView显示界面中if (layoutState.mScrapList == null) { if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { //addView 就是 viewGroup的addView方法 将子view填充到RecylerView中 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); int left, top, right, bottom; if (mOrientation == VERTICAL) { if (isLayoutRTL()) { right = getWidth() - getPaddingRight(); left = right - mOrientationHelper.getDecoratedMeasurementInOther(view); } else { left = getPaddingLeft(); right = left + mOrientationHelper.getDecoratedMeasurementInOther(view); } if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { bottom = layoutState.mOffset; top = layoutState.mOffset - result.mConsumed; } else { top = layoutState.mOffset; bottom = layoutState.mOffset + result.mConsumed; } } else { top = getPaddingTop(); bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view); if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) { right = layoutState.mOffset; left = layoutState.mOffset - result.mConsumed; } else { left = layoutState.mOffset; right = layoutState.mOffset + result.mConsumed; } } // We calculate everything with View's bounding box (which includes decor and margins)// To calculate correct layout position, we subtract margins.//布局子view,这个方法里面调用了child.layout方法,参数就是计算出来的child位置. layoutDecorated(view, left + params.leftMargin, top + params.topMargin, right - params.rightMargin, bottom - params.bottomMargin); if (DEBUG) { Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:" + (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:" + (right - params.rightMargin) + ", b:" + (bottom - params.bottomMargin)); } // Consume the available space if the view is not removed OR changedif (params.isItemRemoved() || params.isItemChanged()) { result.mIgnoreConsumed = true; } result.mFocusable = view.isFocusable(); }
  • 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
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 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
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
这个方法会先从RecyelrVIew缓存中View,然后判断 layoutState.mScrapList是否为null,如果为空,就表示这个view不在移除屏幕的位置,就要进行填充,调用 addView方法,将view填充进来,这个方法内部就式调用 viewGroup的addView方法.
之后调用 measureChildWithMargins(view, 0, 0);对view进行测量,这就是为什么在RecyerlView的构造方法中没有看到对子view的测量,原来在这里测量.
之后调用 layoutDecorated对view进行layout布局, 这个方法里面就是调用了 child.layout方法对控件进行布局.
到了这里,recycleView的填充就此结束了,所有应该在recycleView可见区域的view就被填充到屏幕上了.
ItemDecoraction
我们一般通过 addItemDecoration对分割线进行绘制,谷歌为我们实现的 DividerItemDecoration,其实内部就算调用了系统ListView的分割线样式进行绘制,在 ItemDecoration的onDraw方法中绘制分割线,我们就来研究下这个 ItemDecoration在源码中的体现.
我们都知道一个view的绘制是通过draw方法开始的,所以我们从draw方法查找他的痕迹.
  • draw
@Overridepublicvoiddraw(Canvas c) { super.draw(c); finalint count = mItemDecorations.size(); //ItemDecoration 的数量for (int i = 0; i < count; i++) { //调用ItemDecoration 的onDrawOver 方法绘制ReyclervView的背景 mItemDecorations.get(i).onDrawOver(c, this, mState); } ... }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
darw方法中调用 ItemDecorationonDrawOver,可以看到这个方法是在 super.draw(c);执行完毕后调用的,根据View的绘制逻辑,在 draw方法调用过后,表示系统的绘制流程已经结束了,也就是说这个 onDrawOver是在view的绘制流程全部结束以后调用的方法.
如果看过View的绘制流程,我们知道在 super.draw(c)方法中,会调用onDraw方法进行绘制,这个一般才是绘制内容的方法,我们找一下有没有重写这个方法.
  • onDraw
@OverridepublicvoidonDraw(Canvas c) { super.onDraw(c); finalint count = mItemDecorations.size(); for (int i = 0; i < count; i++) { //绘制内容 mItemDecorations.get(i).onDraw(c, this, mState); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
看到了吗,这里才是真正调用 ItemDecorationonDraw方法的地方,在这个地方,我们就可以调用 ItemDecoration的onDraw方法绘制分割线了.
但是还有个方法需要我们注意,如果我们想要自定义Item的间距怎么办,我们知道 ItemDecoration中有个 getItemOffSets方法可以自定义间距,那么这个方法是怎么发生作用的呢?
根据View的绘制流程,我们猜想,如果要给view添加边距,那么一定会在测量view的时候对padding,margin进行赋值,我们回到刚才的核心填充方法 layoutChunk中对Item测量的方法 measureChildWithMargins(view, 0, 0);.
  • measureChildWithMargins
publicvoidmeasureChildWithMargins(View child, int widthUsed, int heightUsed) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); //用来修改Item的边距 ,也就是说在child.measure之前会先设置好边距final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child); .... child.measure(widthSpec, heightSpec); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • getItemDecorInsetsForChild
Rect getItemDecorInsetsForChild(View child) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (!lp.mInsetsDirty) { return lp.mDecorInsets; } final Rect insets = lp.mDecorInsets; insets.set(0, 0, 0, 0); finalint decorCount = mItemDecorations.size(); for (int i = 0; i < decorCount; i++) { mTempRect.set(0, 0, 0, 0); //这里可以通过ItemDecoration 修改边距 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; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
这样是不是感觉豁然开朗了,原来是这里会调用 getItemOffsets拿到间距,这样就实现了用户自定义布局边距.
ReyclerView的滑动以及ViewHolder机制
如果看过我上一篇 ListView解析,一定会记得 ListView就是在滑动过程中完成的对Item的回收,这里我们将通过对RecyclerView的滑动讲解,进一步的分析ReyclerView中重要的 ViewHolder机制.
在研究源码之前,我们先了解下RecyclerView的滑动状态 : RecyclerView的滑动过程可以分为2个阶段,手指在屏幕上移动,使RecyclerView滑动的过程,可以称为scroll;手指离开屏幕,RecyclerView继续滑动一段距离的过程,可以称为fling。
先看scroll过程,手指没有离开界面,还在滑动过程中
  • onTouchEvent
既然还没有离开界面,那一定在ACITON_MOVE中
case MotionEvent.ACTION_MOVE: { finalint index = MotionEventCompat.findPointerIndex(e, mScrollPointerId); if (index < 0) { Log.e(TAG, "Error processing scroll; pointer index for id " + mScrollPointerId + " not found. Did any MotionEvents get skipped?"); returnfalse; } finalint x = (int) (MotionEventCompat.getX(e, index) + 0.5f); finalint y = (int) (MotionEventCompat.getY(e, index) + 0.5f); if (mScrollState != SCROLL_STATE_DRAGGING) { //计算出手指移动距离finalint dx = x - mInitialTouchX; finalint dy = y - mInitialTouchY; boolean startScroll = false; if (canScrollHorizontally && Math.abs(dx) > mTouchSlop) { //mTouchSlop 滑动阀值 mLastTouchX = mInitialTouchX + mTouchSlop * (dx < 0 ? -1 : 1); startScroll = true; } if (canScrollVertically && Math.abs(dy) > mTouchSlop) { mLastTouchY = mInitialTouchY + mTouchSlop * (dy < 0 ? -1 : 1); startScroll = true; } if (startScroll) { //如果滑动距离大于 阀值得话, scrollState 就为SCROLL_STATE_DRAGGING setScrollState(SCROLL_STATE_DRAGGING); } } if (mScrollState == SCROLL_STATE_DRAGGING) { finalint dx = x - mLastTouchX; finalint dy = y - mLastTouchY; //滑动 第一阶段scroll完成if (scrollByInternal( canScrollHorizontally ? -dx : 0, canScrollVertically ? -dy : 0)) { getParent().requestDisallowInterceptTouchEvent(true); } } mLastTouchX = x; mLastTouchY = y; } break;
  • 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
  • 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
这个方法中会进行一些对滑动的判断,只要滑动有效,就会调用 scrollByInternal方法.
  • scrollByInternal
//滑动boolean scrollByInternal(int x, int y) { int overscrollX = 0, overscrollY = 0; int hresult = 0, vresult = 0; consumePendingUpdateOperations(); if (mAdapter != null) { eatRequestLayout(); mRunningLayoutOrScroll = true; if (x != 0) { //调用LayoutManager的scrollHorzontallBy方法 hresult = mLayout.scrollHorizontallyBy(x, mRecycler, mState); overscrollX = x - hresult; } if (y != 0) { //调用LayoutManager的scrollVerticallyBy方法 vresult = mLayout.scrollVerticallyBy(y, mRecycler, mState); overscrollY = y - vresult; } ... } .... return hresult != 0 || vresult != 0; }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
可以看到这里通过对滑动方向的判断调用相对应的滑动方法,如果是我们是垂直滑动的话,会调用 mLayout.scrollVerticallyBy(y, mRecycler, mState);方法,也就说具体的滑动逻辑也是由 LayoutManager处理的.
我们来看看 LinearLayoutManagerscrollVerticallyBy方法
  • scrollVerticallyBy
@OverridepublicintscrollVerticallyBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { if (mOrientation == HORIZONTAL) { return0; } return scrollBy(dy, recycler, state); }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
调用 scrollBy(dy, recycler, state);方法
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) { if (getChildCount() == 0 || dy == 0) { return0; } mLayoutState.mRecycle = true; ensureLayoutState(); finalint layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START; finalint absDy = Math.abs(dy); updateLayoutState(layoutDirection, absDy, true, state); finalint freeScroll = mLayoutState.mScrollingOffset; finalint consumed = freeScroll + fill(recycler, mLayoutState, state, false); if (consumed < 0) { if (DEBUG) { Log.d(TAG, "Don't have any more elements to scroll"); } return0; } finalint scrolled = absDy > consumed ? layoutDirection * consumed : dy; //如上文所讲到的fill()方法,作用就是向可绘制区间填充ItemView//,那么在这里,可绘制区间就是滑动偏移量!再看方法mOrientationHelper.offsetChildren()作用就是平移ItemView。//平移ItemView。 这里就完成了移动 mOrientationHelper.offsetChildren(-scrolled); if (DEBUG) { Log.d(TAG, "scroll req: " + dy + " scrolled: " + scrolled); } return scrolled; }
  • 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
  • 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
看到了吗,这个方法中,会调用fill方法对向前界面的Item进行填充,最后再对Item进行平移,这里我们回到 fill方法,我们回一下其中的核心填充方法 layoutChunk,其中有这么一行 View view = layoutState.next(recycler);,这个方法会实现从RecyclerView的复用机制中获取View,我们这里研究下这个方法是怎么实现的,这个方法内部会调用 getViewForPosition方法进行具体的获取操作.
  • getViewForPosition
这里有这么几个需要注意的复用对象 :
<1>scrapped : 从RecyclerView中删除的view
<2>cached : 是ItemView的一级缓存,cached集合的大小默认为2
<3>exCached : 是ItemView的二级缓存,exCached是需要我们通过RecyclerView.ViewCacheExtension自己实现的,默认没有
<4>recycled : 集合其实是一个Map,定义在RecyclerView.RecycledViewPool中将ItemView以ItemType分类保存了下来,这里算是RecyclerView设计上的亮点,通过RecyclerView.RecycledViewPool可以实现在不同的RecyclerView之间共享ItemView,只要为这些不同RecyclerView设置同一个RecyclerView.RecycledViewPool就可以了。
//获取某个位置需要展示的View,先检查是否有可复用的View,没有则创建新View并返回。具体过程为: //step1 检查mChangedScrap,若匹配到则返回相应holder //step2 检查AttachedScrap,若匹配到且holder有效则返回相应holder //step3 查mViewCacheExtension,若匹配到则返回相应holder //step4 检查mRecyclerPool,若匹配到则返回相应holder //step5 否则执行Adapter.createViewHolder(),新建holder实例 //step6 返回holder.itemView //step7 注:以上每步匹配过程都可以匹配position或itemId(如果有stableId)public View getViewForPosition(int position) { return getViewForPosition(position, false); } //根据列表位置获取ItemView,先后从scrapped、cached、exCached、recycled//集合中查找相应的ItemView,如果没有找到,就创建(Adapter.createViewHolder()),最后与数据集绑定。 View getViewForPosition(int position, boolean dryRun) { if (position < 0 || position >= mState.getItemCount()) { thrownew IndexOutOfBoundsException("Invalid item position " + position + "(" + position + "). Item count:" + mState.getItemCount()); } boolean fromScrap = false; ViewHolder holder = null; //先后从scrapped、cached、exCached、recycled集合中查找相应的ItemView// 0) If there is a changed scrap, try to find from thereif (mState.isPreLayout()) { //检查mChangedScrap,若匹配到则返回相应holder holder = getChangedScrapViewForPosition(position); fromScrap = holder != null; } // 1) Find from scrap by position if (holder == null) { //检查AttachedScrap,若匹配到且holder有效则返回相应holder holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun); if (holder != null) { if (!validateViewHolderForOffsetPosition(holder)) { // recycle this scrapif (!dryRun) { // we would like to recycle this but need to make sure it is not used by// animation logic etc. holder.addFlags(ViewHolder.FLAG_INVALID); if (holder.isScrap()) { removeDetachedView(holder.itemView, false); holder.unScrap(); } elseif (holder.wasReturnedFromScrap()) { holder.clearReturnedFromScrapFlag(); } recycleViewHolderInternal(holder); } holder = null; } else { fromScrap = true; } } } if (holder == null) { finalint offsetPosition = mAdapterHelper.findPositionOffset(position); if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) { thrownew IndexOutOfBoundsException("Inconsistency detected. Invalid item " + "position " + position + "(offset:" + offsetPosition + ")." + "state:" + mState.getItemCount()); } finalint type = mAdapter.getItemViewType(offsetPosition); // 2) Find from scrap via stable ids, if existsif (mAdapter.hasStableIds()) { //一级缓存,先检查一级缓存, 如果有就返回viewHolder holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); if (holder != null) { // update position holder.mPosition = offsetPosition; fromScrap = true; } } if (holder == null && mViewCacheExtension != null) { // We are NOT sending the offsetPosition because LayoutManager does not// know it.//二级缓存, 是开发者自定义的缓存, 从二级缓存中拿到ItemViewfinal View view = mViewCacheExtension .getViewForPositionAndType(this, position, type); if (view != null) { holder = getChildViewHolder(view); if (holder == null) { thrownew IllegalArgumentException("getViewForPositionAndType returned" + " a view which does not have a ViewHolder"); } elseif (holder.shouldIgnore()) { thrownew IllegalArgumentException("getViewForPositionAndType returned" + " a view that is ignored. You must call stopIgnoring before" + " returning this view."); } } } if (holder == null) { // fallback to recycler// try recycler.// Head to the shared pool.if (DEBUG) { Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared " + "pool"); } //检查mRecyclerPool,若匹配到则返回相应holder holder = getRecycledViewPool() .getRecycledView(mAdapter.getItemViewType(offsetPosition)); if (holder != null) { holder.resetInternal(); if (FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt(holder); } } } if (holder == null) { //否则执行Adapter.createViewHolder(),新建holder实例 holder = mAdapter.createViewHolder(RecyclerView.this, mAdapter.getItemViewType(offsetPosition)); if (DEBUG) { Log.d(TAG, "getViewForPosition created new ViewHolder"); } } } boolean bound = false; if (mState.isPreLayout() && holder.isBound()) { // do not update unless we absolutely have to. holder.mPreLayoutPosition = position; } elseif (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) { if (DEBUG && holder.isRemoved()) { thrownew IllegalStateException("Removed holder should be bound and it should" + " come here only in pre-layout. Holder: " + holder); } finalint offsetPosition = mAdapterHelper.findPositionOffset(position); mAdapter.bindViewHolder(holder, offsetPosition); attachAccessibilityDelegate(holder.itemView); bound = true; if (mState.isPreLayout()) { holder.mPreLayoutPosition = position; } } final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); final LayoutParams rvLayoutParams; if (lp == null) { rvLayoutParams = (LayoutParams) generateDefaultLayoutParams(); holder.itemView.setLayoutParams(rvLayoutParams); } elseif (!checkLayoutParams(lp)) { rvLayoutParams = (LayoutParams) generateLayoutParams(lp); holder.itemView.setLayoutParams(rvLayoutParams); } else { rvLayoutParams = (LayoutParams) lp; } rvLayoutParams.mViewHolder = holder; rvLayoutParams.mPendingInvalidate = fromScrap && bound; //返回holder.itemView return holder.itemView; }
  • 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
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 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
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
上面注释的已经十分清楚,就是从RecyclerView的几个缓存中去获取,一级一级向下取,最后如果没有,这时候会调用 AdaptercreateViewHolder来创建ViewHolder,这个就是自己创建的ViewHolder,最后这个方法返回ViewHolder中存放的 ItemView就拿到了每个Item的View对象.
如果有向缓存中取,那么一定有存,我们来看看这个复用机制是怎么存的,在 fill中,有这么一个方法在 layoutChunk执行之后, recycleByLayoutState,这个方法向里面一直调用,最终会有个 recycleView方法,看名字真有那么点意思,里面最后调用了 recycleViewHolderInternal方法,这个就是RecyclerView最终调用的添加方法,我们一起来分析看看.
  • recycleViewHolderInternal
void recycleViewHolderInternal(ViewHolder holder) { ... if (forceRecycle || holder.isRecyclable()) { boolean cached = false; if (!holder.isInvalid() && (mState.mInPreLayout || !holder.isRemoved()) && !holder.isChanged()) { // Retire oldest cached viewfinalint cachedViewSize = mCachedViews.size(); //首先判断cachedView 是否满了 最大mViewCacheMax = 2如果已満就从cached集合中移出一个到recycled集合中去,再把新的ItemView添加到cached集合if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) { //如果满了 就从cachedViews中移除一个, recycleCachedViewAt(0); } if (cachedViewSize < mViewCacheMax) { //再把新的ItemView添加到cached集合 mCachedViews.add(holder); cached = true; } } if (!cached) { //如果没有被缓存 缓存到recycleViewPool中 addViewHolderToRecycledViewPool(holder); } } elseif (DEBUG) { Log.d(TAG, "trying to recycle a non-recycleable holder. Hopefully, it will " + "re-visit here. We are stil removing it from animation lists"); } // even if the holder is not removed, we still call this method so that it is removed// from view holder lists. mState.onViewRecycled(holder); }
  • 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
  • 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
这个注释分析的很清楚,就是对ViewHolder的一个存储过程.到这里,就将 RecyclerView的复用机制分析完成.
RecyclerView动画
RecyclerView定义了4种针对数据集的操作,分别是ADD、REMOVE、UPDATE、MOVE,封装在了AdapterHelper.UpdateOp类中,并且所有操作由一个大小为30的对象池管理着。当我们要对数据集作任何操作时,都会从这个对象池中取出一个UpdateOp对象,放入一个等待队列中,最后调用
RecyclerView.RecyclerViewDataObserver.triggerUpdateProcessor()方法,根据这个等待队列中的信息,对所有子控件重新测量、布局并绘制且执行动画。以上就是我们调用Adapter.notifyItemXXX()系列方法后发生的事。
我们以remove操作为例 :
  • notifyItemRemove()
publicfinalvoidnotifyItemRemoved(int position) { mObservable.notifyItemRangeRemoved(position, 1); }
  • 1
  • 2
  • 3
  • 1
  • 2
  • 3
调用被观察者的notifyItemRangeRemoved方法,我们到RecyclerView的被观察者 AdapterDataObservable中看看.
publicvoidnotifyItemRangeRemoved(int positionStart, int itemCount) { // since onItemRangeRemoved() 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).onItemRangeRemoved(positionStart, itemCount); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
这里调用了观察者 RecyclerViewDataObserveronItemRangeRemoved的方法
@OverridepublicvoidonItemRangeRemoved(int positionStart, int itemCount) { assertNotInLayoutOrScroll(null); //第一步 将 removed信息放到集合中,对象为UpdateOpif (mAdapterHelper.onItemRangeRemoved(positionStart, itemCount)) { triggerUpdateProcessor(); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
void triggerUpdateProcessor() { if (mPostUpdatesOnAnimation && mHasFixedSize && mIsAttached) { ViewCompat.postOnAnimation(RecyclerView.this, mUpdateChildViewsRunnable); } else { mAdapterUpdateDuringMeasure = true; //界面重新布局,也就式调用 RecyclerView的onLayout方法 requestLayout(); } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
这里我们又回到了onLayout方法中,还记得我们之前省略过一部分吗,就是在那里实现的
  • dispatchLayout
void dispatchLayout() { ... 上面为填充方法,已经分析过.... if (mState.mRunSimpleAnimations) { //removed动画int preLayoutCount = mState.mPreLayoutHolderMap.size(); for (int i = preLayoutCount - 1; i >= 0; i--) { ViewHolder itemHolder = mState.mPreLayoutHolderMap.keyAt(i); if (!mState.mPostLayoutHolderMap.containsKey(itemHolder)) { ItemHolderInfo disappearingItem = mState.mPreLayoutHolderMap.valueAt(i); mState.mPreLayoutHolderMap.removeAt(i); View disappearingItemView = disappearingItem.holder.itemView; mRecycler.unscrapView(disappearingItem.holder); //执行动画 remove动画 animateDisappearance(disappearingItem); } } ...其他动画操作... } }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
这里调用了 animateDisappearance执行removed动画
privatevoidanimateDisappearance(ItemHolderInfo disappearingItem) { View disappearingItemView = disappearingItem.holder.itemView; addAnimatingView(disappearingItem.holder); int oldLeft = disappearingItem.left; int oldTop = disappearingItem.top; int newLeft = disappearingItemView.getLeft(); int newTop = disappearingItemView.getTop(); if (oldLeft != newLeft || oldTop != newTop) { disappearingItem.holder.setIsRecyclable(false); disappearingItemView.layout(newLeft, newTop, newLeft + disappearingItemView.getWidth(), newTop + disappearingItemView.getHeight()); if (DEBUG) { Log.d(TAG, "DISAPPEARING: " + disappearingItem.holder + " with view " + disappearingItemView); } if (mItemAnimator.animateMove(disappearingItem.holder, oldLeft, oldTop, newLeft, newTop)) { postAnimationRunner(); } } else { if (DEBUG) { Log.d(TAG, "REMOVED: " + disappearingItem.holder + " with view " + disappearingItemView); } disappearingItem.holder.setIsRecyclable(false); //remove动画,这就进行了动画执行,这里的动画就是用户自定义的实现方式if (mItemAnimator.animateRemove(disappearingItem.holder)) { postAnimationRunner(); } } }
  • 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
  • 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
这里最后调用了 mItemAnimator.animateRemove(disappearingItem.holder)方法,如果自定义过动画的朋友一定会知道,我们通过集成 ItemAnimator,重写它的animateXXX(ViewHolder holder) 中拿到holder.itemView 就能通过这个View进行动画操作了.
到这里,所有关于RecyclerView的源码机械就到这里结束了,我们可以看出,RecyclerView相对于ListView有更好的模块化,更加低耦合,让用户可以通过它提供的各种接口游刃有余的自定义RecyclerView的各种样式,并且考虑到使用者根本没有必要对View的复用有所关注所以加入了ViewHolder机制,这种思想真是值得我们深入学习.
后面我还会对ViewPager,Behavor 进行源码解析,希望通过这种方式能够对自定义View有更深入的理解.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值