RecyclerView 源码分析

前言

如题,以往有一种behavior的运用境况:NestedScrollView下边包裹横向的RecyclerView,behavior的滚动回调方法不实践。详细可知demo,
建议最佳clone下来自个儿试一试,因为您有朝一日会用到behavior!看看难点

图片 1滚动下边bottomView未有跟着动

  • 先来拜候demo的布局层级

    图片 2main_activityCoordinatorLayout包蕴五个子View:
    Viewpager和View(注入behavior关联滚动的view)

  • 再看看viewpager_item

    图片 3viewpager_item

    中间是一层NestedScrollView,里面蕴含多少个子Linear,
    Linear里面包裹横向的RecyclerView

  • 聊到底层级图

图片 4成效希望滚动里面包车型地铁nestedscrollView然后来得和隐敝bottomView

那么些层级依然简化后的demo的,实际支付中我们相遇的景色比这几个尤其错综复杂,但是纵然层级再多再繁杂,只要相符behavior的使用准绳,那么任何皆能够兑现。

  • 再看看behavior

public class MyBehavior extends CoordinatorLayout.Behavior<View> { private boolean isHide = false; public MyBehavior(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onStartNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View directTargetChild, @NonNull View target, int axes, int type) { return true; } @Override public void onNestedScroll(@NonNull CoordinatorLayout coordinatorLayout, @NonNull View child, @NonNull View target, int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int type) { Log.e("test", "onNS"); if(dyConsumed >0 ) { if  { child.offsetTopAndBottom(child.getHeight; isHide = true; } }else { if{ child.offsetTopAndBottom(-child.getHeight; isHide = false; } } }}

也顶尖简单正是剖断一下轮转方向,然后彰显和隐敝bottomView而已。

项目供给,商品详细情形页

Recycler:

本文原创,转发请注解出处。迎接关心作者的 简书 ,关切本人的专项论题 Android
Class 作者会长时间坚贞不屈为我们收音和录音简书上高素质的 Android 相关博文。

闲聊的导语

RecyclerView能够说是每一个品种必须运用的,可是众三人只知其常用用法,上边小编将经过那篇小说实行深切摸底。

但是

大家这么轻易的代码却有着难题,大家实际上运作发掘,貌似滚动的涉嫌“不太灵活”,打log开掘,不常候onNestedScroll方法不会调用。那是干吗呢?

图片 5

        Recycler的天职是管制那些早已吐弃了的要么从RecyclerView中分其他item
view用于复用。Recycler标准的用法就是当LayoutManager去获得艾达pter中的某一项View的时候,若是这一个View失效了,则须求再行绑定View,当复用的View是行得通的话,View就能被直接被复用。有效的View要是不主动调用requestLayout,那么该View没有须要再行度量就能够被复用。

写在头里:RecyclerView 是七个越用越尊贵的控件,相信大家对于
RecyclerView 的运用也曾经相比较明白了。其效力的惊人解耦化,标准 ViewHolder
的写法,以及对动画片友好协理,都以它与历史观控件 ListView 的区分。而不论是
ListView 依旧RecyclerView,本质上都以在少数的荧屏之上,体现多量的原委。所以复用的逻辑,就成了它们最最重大的主导原理,本文重要指标正是追究
RecyclerView 的复用原理。


问题

于是提出两个难点:1、为何onNestedScroll方法不会调用?2、为啥让RecyclerView设置setNestedScrollingEnable就可以寻常使用?

其他后边会进行更加深档案的次序的源码解析,附加多少个难题:1、对于假若onIntercept重临true拦截了,交给onTouch伊夫nt去管理,具体映将来哪儿?2、剖断子View是或不是能够吸取事件从何地显示?3、其它三个十分主要的艺术dispatchTransformedTouch伊芙nt干什么用的?4、viewGroup和view的dispatch再次来到false,会一贯回溯到parent的onTouchEvent,那些又在哪个地方展示?5、viewGroup重写了dispatch不过从未调用super,
那么它在哪儿调用自个儿的onTouch的吧?

详情.png

图片 6

  • LayoutManager肩负 RecyclerView 中,调整 item 的布局方向

  • RecyclerView.Adapter为 RecyclerView 承载数据

  • ItemDecoration为 RecyclerView 增多分界线

  • ItemAnimator控制 RecyclerView 中 item 的动画

RecyclerView的安排性指标

探讨一个共处的控件,先看看那么些控件的统一希图目标是哪些。其实正是拜候RecyclerView是为啥的。官方对RecyclerView的牵线是很轻巧的一句话:

A flexible view for providing a limited window into a large data
set.

二个用来为大气数目集结提供轻松窗口的灵巧的视图。笔者的翻译有一点怪,本身看乌克兰语精晓。介绍切中要害,一语破的…好,我没词儿了。

中间主要的有两点:

–  providing a limited window into a large data set

 – flexible

本着与这两点大家得以看看RecyclerView的总体布署。

正题

平凡在做详情页的时候,难免须求用到ScrollView嵌套RecyclerView,ScrollView嵌套RecyclerView会存在展现不全的难题,滑动亦不是太流利,
网络有无数解决滑动争辨的措施,然而我前天带来的是一种相比高贵凝练的艺术,NestedScrollView嵌套RecyclerView,不会设有显示不全的主题素材

Recycler多少个成员变量

凑巧我们关系 RecyclerView 的莫斯中国科学技术大学学解耦,便是经过上述指标各司其职,来兑现
RecyclerView 的基本功效。RecyclerView 无论多么复杂,本质上也是三个自定义
View,本文的要害便是缓存原理剖判,不过以前,大家依然轻巧地分别介绍下以上中逐个模块的源码。

RecyclerView数据显示的宏图思路

···

providing a limited window into a large data set

providing a limited window into a large data set

···

先是我们化解第三个难题,“为啥onNestedScroll未有调用?”

这亟需大家对behavior有确定的打听,大家都知道coordinatorLayout和behavior联合利用可以兑现广大鲜艳的效果与利益,很牛逼。

behavior的干活原理正是:1、coordinatorLayout上边包车型客车具有子view,完结了滚动接口(富含NestedScrollingChild、NestedScrollingParent等等)的view,
假诺有滑动事件的损耗,就能够一层一层进步传递,直到coordinatorLayout2、然后coordinatorLayout再对流入了behavior的子View传递滚动回调事件,那样,behavior就会得到滚动的值,进而拓宽对View的局部关乎滚动操作借使用最通俗的事例来说正是:阿爹是CoordinatorLayout,它有多个外甥,七个是NestedScrollView,两个是BottomView,behavior绑在BottomView身上。NestedScrollView发年底奖了,发了红包给老爸,然后阿爸又把钱分给了喜爱的外孙子BottomView(老爸又公告了BottomView)贴点主要代码recycler->linear->nestedScroll->coordinator:

图片 7传送进度,别的非滚动view“透明”图片 8十三分中回到false

何以onNestedScroll未有回调呢?PS:
这里的源码是对应26的,support是26.1
通过在代码里面打断点开掘:

  • 图片 9RecyclerView中温馨花费了consumedY

RecyclerView中友好花费了consumedY,uncomsumed = y – consumeY = 0 ,然后

图片 10NestedScrollView中得到的dyUnConsumed为0

NestedScrollView中获得的dyUnConsumed为0,调用dispatchNestedScroll方法也就传入0

图片 11NestedScrollingChildHelper中if分支进不去

这样的话,NestedScrollingChildHelper中if分支进不去,就无法向上层传递花费的y值(约等于它并从未滚动),ViewParentCompat.onNestedScroll无法调用,所以未能传递到顶层的CoordinatorLayout,自然behavior里面也不会吸收回调了。

从源码上来看是这般的,假诺从微观上来说,其实正是RecyclerView和NestedScrollView的事件管理有争辨,RecyclerView花费了风云,进而NestedScrollView未能把温馨花费的轩然大波往上传递。按道理,大家都晓得,假设竖向的RecyclerView和NestedScrollView大概ScrollView联合利用的话(即便,这样一同使用未有意义,也不提议如此做),会师世风波争持。可是,横向的RecyclerView和NestedScrollView一同使用,在事件处理上边是一贯不难点的,未有争辨,可是,在采用到behavior,希望nestedScrollView能够把团结滚动花费的风浪往上传递的时候就能出难题了。(大家都指望behavior的利用是在尚未嵌套滚动抵触的气象下,兄弟滚动,然后老爸知道,老爹布告别的八个兄弟做出相应的一颦一笑,而只假使后人滚动,往上传给老爹,那之间出了难点,就无可奈何符合规律办事了)

xml:

RecycledViewPool:RecycledViewPool让开荒者可以在三个RecyclerView之间分享View。假设你想要跨RecyclerView复用View,成立叁个RecycledViewPool实例,然后调用setRecycledViewPool(RecycledViewPool)方法就足以了。RecyclerView会自动创设二个RecycledViewPool的实例。有了RecycledViewPool的存在,就能够十分的大程度上压缩View的创办,提升品质。

 public void addItemDecoration(ItemDecoration decor, int index) { if (mLayout != null) { mLayout.assertNotInLayoutOrScroll("Cannot add item decoration during a scroll or" + " layout"); } if (mItemDecorations.isEmpty { setWillNotDraw; } if (index < 0) { mItemDecorations.add; } else { // 指定添加分割线在集合中的索引 mItemDecorations.add(index, decor); } markItemDecorInsetsDirty(); // 重新请求 View 的测量、布局、绘制 requestLayout(); }
继而首个难点,“为啥让RecyclerView设置setNestedScrollingEnable就可见健康使用?”

看看效果

图片 12OK,或者转gif帧率非常不足有一点点卡

第三个难点就需求我们对那一件事件分发机制有早晚的打听,这里就大致贴张图。

图片 13事件分发机制其余,贴多少个感觉相比不错的链接:1、图解
Android
事件分发机制2、Android事件分发机制详解:史上最健全、最易懂3、Android6.0源码解读之View点击事件分发机制4、Android
事件分发机制-试着读懂每一行源码-View5、ScrollView与头+RecycleView嵌套争论源码解析

咱俩看看setNestedScrollingEnabled

// RecyclerView @Override public void setNestedScrollingEnabled(boolean enabled) { getScrollingChildHelper().setNestedScrollingEnabled; }

调用了扶持类

// NestedScrollingChildHelper public void setNestedScrollingEnabled(boolean enabled) { if (mIsNestedScrollingEnabled) { ViewCompat.stopNestedScroll; } mIsNestedScrollingEnabled = enabled; }

协助类设置mIsNestedScrollingEnabled为false,况兼调用了
ViewCompat.stopNestedScroll;传入了和睦

// NestedScrollingChildHelper public boolean isNestedScrollingEnabled() { return mIsNestedScrollingEnabled; }

这么isNestedScrollingEnabled重临false了,以后behavior的回调方法里面包车型大巴if(isNestedScrollingEnabled就进不去了随后:

//ViewCompat public static void stopNestedScroll(@NonNull View view) { IMPL.stopNestedScroll; }

这里,ViewCompat正是多个宽容类,包容各类版本api的运用,因为有局地新本子的api,完成的是NestedScrollingParent2等措施。

//ViewCompat public void stopNestedScroll(View view) { if (view instanceof NestedScrollingChild) { ((NestedScrollingChild) view).stopNestedScroll(); } }

这里是一对一于调用view的stopNestedScroll,也便是RecyclerView的。

//RecyclerView @Override public void stopNestedScroll() { getScrollingChildHelper().stopNestedScroll(); }

//NestedScrollingChildHelper public void stopNestedScroll() { stopNestedScroll(TYPE_TOUCH); }

//NestedScrollingChildHelper public void stopNestedScroll(@NestedScrollType int type) { ViewParent parent = getNestedScrollingParentForType; if (parent != null) { ViewParentCompat.onStopNestedScroll(parent, mView, type); setNestedScrollingParentForType(type, null); } }

此处就相比关键了,这里通过getNestedScrollingParenForType获得了parent,然后调用了ViewParentCompat.onStopNestedScroll(parent,
mView, type);

//viewParentCompat public static void onStopNestedScroll(ViewParent parent, View target, int type) { if (parent instanceof NestedScrollingParent2) { // First try the NestedScrollingParent2 API ((NestedScrollingParent2) parent).onStopNestedScroll(target, type); } else if (type == ViewCompat.TYPE_TOUCH) { // Else if the type is the default , try the NestedScrollingParent API IMPL.onStopNestedScroll(parent, target); } }

本条方法会又调用IMPL.onStopNestedScroll(parent,
target);那样好像的点子其实正是把事件一层一层往上传,当然,其余onPreNestedScroll、onNestedScroll这个也都以如此的。

//NestesScrollView @Override public void onStopNestedScroll(View target) { mParentHelper.onStopNestedScroll; stopNestedScroll(); }

我们又看NestedScrollView里面包车型客车onStopNestedScrollstopNestedScroll();是后续往上调用传递mParentHelper.onStopNestedScroll;就很主要了

//NestedScrollingParentHelper public void onStopNestedScroll(@NonNull View target) { onStopNestedScroll(target, ViewCompat.TYPE_TOUCH); }

 public void onStopNestedScroll(@NonNull View target, @NestedScrollType int type) { mNestedScrollAxes = 0; }

以此就至关心敬服要了,mNestedScrollAxes = 0

 return mNestedScrollAxes; }

其一法子重临0了,看看它在哪被调用

//NestedScrollVIew#onIntercept#move final int yDiff = Math.abs(y - mLastMotionY); if (yDiff > mTouchSlop && (getNestedScrollAxes() & ViewCompat.SCROLL_AXIS_VERTICAL) == 0) { mIsBeingDragged = true; mLastMotionY = y; initVelocityTrackerIfNotExists(); mVelocityTracker.addMovement; mNestedYOffset = 0; final ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent; } } break;

在move的时候,它回到为0,那么步入分支的话,mIsBeingDragged
=trueonInterceptTouchEvent就回去true,
就能阻碍了。那就表达,在move的时候,nestedScrollView就全盘挡住了事件,里面包车型客车子孙view(包涵横向的RecyclerView就不会有事件了,更毫不谈怎么样它自身开支掉了consumeY,NestedScrollView自个儿全权处理了),那样的话它和煦的滚动事件就可以再往上一直传递到coordinatorLayout,然后behavior也就一定能够实践回来方法了!啊,原来那样,峰回路转!

<android.support.v4.widget.NestedScrollView
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

       <!--其他控件-->
        <TextView
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:text="other"/>

        <android.support.v7.widget.RecyclerView
            android:id="@+id/recyclerView"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"/>

    </LinearLayout>

</android.support.v4.widget.NestedScrollView>

图片 14

mItemDecorations 是一个 ArrayList,我们将 ItemDecoration
也正是分水岭对象,加多到中间。接着我们看下 markItemDecorInsetsDirty
那一个方法做了些什么。

5个小标题

前边五个大主题材料总算消除了,上面来搞精通前边提的那5个小标题。1、对于若是onIntercept再次回到true拦截了,交给onTouch伊夫nt去管理,具体突显在哪个地方?2、剖断子View是还是不是还可以事件从哪里显示?3、别的二个比较重大的格局dispatchTransformedTouchEvent干什么用的?4、viewGroup和view的dispatch重返false,会向来回溯到parent的onTouchEvent,那个又在哪里展现?5、viewGroup重写了dispatch不过尚未调用super,
那么它在什么地方调用自个儿的onTouch的啊?
那多少个难题全都以有关事件分发的,我们能够把这些链接的小说都看了,假如还不能够缓和,那么再往下看。

//ViewGroup#dispatchTouchEvent 代码有省略 @Override public boolean dispatchTouchEvent(MotionEvent ev) { boolean handled = false; // Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { ##### 重点 // 这里mFirstTouchTarget置为null cancelAndClearTouchTargets; resetTouchState(); } // Check for interception. final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { intercepted = onInterceptTouchEvent; ev.setAction; // restore action in case it was changed } else { intercepted = false; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true; } final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; ##### 重要 if分支1 if (!canceled && !intercepted) { final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = getAndVerifyPreorderedIndex( childrenCount, i, customOrder); final View child = getAndVerifyPreorderedView( preorderedList, children, childIndex); // If there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. We may do a double iteration but this is // safer given the timeframe. if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } ##### 重点 if (!canViewReceivePointerEvents || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus; continue; } resetCancelNextUpFlag; ##### 重点 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // Child wants to receive touch within its bounds. mLastTouchDownTime = ev.getDownTime(); if (preorderedList != null) { // childIndex points into presorted list, find original index for (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); ##### 重点 newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } // The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus; } if (preorderedList != null) preorderedList.clear(); } } // Dispatch to touch targets. ##### 重要 if分支2 if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { // Dispatch to touch targets, excluding the new touch target if we already // dispatched to it. Cancel touch targets if necessary. TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; ##### 重点 if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } ... return handled; }

//ViewGroup#dispatchTransformedTouchEvent 代码有省略 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent; } else { handled = child.dispatchTouchEvent; } event.setAction(oldAction); return handled; } final MotionEvent transformedEvent; if (newPointerIdBits == oldPointerIdBits) { if (child == null || child.hasIdentityMatrix { if (child == null) { handled = super.dispatchTouchEvent; } else { handled = child.dispatchTouchEvent; } return handled; } transformedEvent = MotionEvent.obtain; } else { transformedEvent = event.split(newPointerIdBits); } // Perform any necessary transformations and dispatch. if (child == null) { handled = super.dispatchTouchEvent(transformedEvent); } else { handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; }

设若onIntercep重返true,那么interceped变量为true,那么不会步入(里面分发事件,设置mFirstTouchTarget等),mFirstTouchTarget还是为null,
于是步入的dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS)
并且传入child为null,在dispatchTransformedTouchEvent中只要child为null,就能够走super.dispatch,
super就是view,这样的话,就能走view的dispatch(view自己的dispatch会调onTouch),就能走到onTouch去了

在viewgroup的dispatch中的进入if分支之后,里面有个决断

if (!canViewReceivePointerEvents || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus; continue; }

有个格局canViewReceivePointerEvents,里面根本是决断是或不是VisibleisTransformedTouchPointInView首假如判断事件的职分是否在子VIew的区域内假诺那多少个,就continue,后续的平地风波分发就不开展

dispatchTransformedTouchEvent首要正是对于事件分发的拍卖,譬喻如哪一天候调用自身的super.dispatch,什么日期调用child.disaptch分发给子View,
这一个判别方法的最重要依附就是child是或不是为null,
而这些又跟mFirstTouchTarget有涉及

以此主题素材也跟第4个难点不怎么类似,假如子View的dispatch重回false,那么dispatchTransformedTouch伊夫nt的handled就能是false重返,然后就走不进去,addTouchTarget那么些法子也不实施(首要是给mFirstTarget赋值)

 private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }

前方说了,假使mFirstTarget为null,
就能够进去dispatchTransformedTouch伊夫nt的时候传出为null的child,
那样就能够调用super.dispatch,正是view的dispatch,然后就调用了onTouch咯

一经看见那,希望第5个难点作者早已不用解释了,因为前面4个难点早就把它满含在内了。

地点的代码只是简短的布局嵌套而已,可是还应该有二个小标题,触摸到RecyclerView的时候滑动还会有不通畅,只需

mScrap是贰个<viewType, List>的炫人眼目,m马克斯Scrap是三个<viewType,
maxNum>的照射,我们能够调用set马克斯RecycledViews方法来设置各类viewType的view体量。从源码能够见到,倘诺viewType类型的list的size大于制定的最大数字来讲,会先从列表的末段开头丢弃超越的部分。调用getRecycledView(int
viewType)方法呢,能够将scrapHeap中的最终一项移除并回到viewType对应的List的末尾项。这里供给小心的是,因为是跨RecyclerView举办操作,所以要特别注意对于同一个RecycledViewPool,对ViewType的定义要联合,因为这里是基于viewType来取ViewHolder的。 

 void markItemDecorInsetsDirty() { final int childCount = mChildHelper.getUnfilteredChildCount(); for (int i = 0; i < childCount; i++) { final View child = mChildHelper.getUnfilteredChildAt; ((LayoutParams) child.getLayoutParams.mInsetsDirty = true; } mRecycler.markItemDecorInsetsDirty(); }

最后

为了化解那一个题材,近来径直在源码的黑洞里遨游,打了N个断点来回跳,梳理逻辑。最终一句,最后想要深入地驾驭事件分发机制、behavior机制那么些玩具,RTFSC。

//布局文件的RecyclerView中设置
android:nestedScrollingEnabled="false" 
//或者Java代码设置
recyclerView.setNestedScrollingEnabled(false);

ViewCacheExtension:

以此办法首先遍历了 RecyclerView 和 LayoutManager 的兼具子 View,将其子
View 的 LayoutParams 中的 mInsetsDirty 属性置为 true。接着调用了
mRecycler.markItemDecorInsetsDirty(),Recycler 是 RecyclerView
的三个之中类,正是它管理着 RecyclerView 的复用逻辑。这几个大家一会再细谈。

到此滑动争持和展现不全的主题素材就能够化解了,不过也在此表达有个别,这种措施只是适用于ScrollView中嵌套的可怜RecyclerView的内容不是特意多,就像作者的类型中,那个评价列表只呈现几条,这种情景下,用这几个措施不失为一个尊贵的消除措施。为何数据量大的时候就无须接纳这种措施了啊?案由是:NestedScrollView+RecyclerView在展现上没什么难题,但会使RecyclerView在开首化就将富有的item都加载出来,换句话说,recyclerVIew的复用机制就不起功用了,所以看等级次序供给呢

 void markItemDecorInsetsDirty() { final int cachedCount = mCachedViews.size(); for (int i = 0; i < cachedCount; i++) { final ViewHolder holder = mCachedViews.get; LayoutParams layoutParams = (LayoutParams) holder.itemView.getLayoutParams(); if (layoutParams != null) { layoutParams.mInsetsDirty = true; } } }

mCachedViews 见名知意,相当于 RecyclerView
缓存的聚合,相信您也看出了,RecyclerView 的缓存单位是 ViewHolder。大家在
ViewHolder 中抽出 itemView,然后拿走 LayoutParams,将其 mInsetsDirty
字段同样置为 true。

mInsetsDirty
字段的效能其实是一种优化质量的缓存攻略,增多分水岭对象时,无论是
RecyclerView 的子 view,依然缓存的 view,都将其置为 true,接着就调用了
requestLayout 方法。

此地质大学致说一下 requestLayout
方法用一种权利链的诀要,层层进步传递,最终传递到
ViewRootImpl,然后再度调用 view 的 measure、layout、draw
方法来展现布局。

大家在 RecyclerView 中寻觅 mItemDecorations 集结,看看她是在什么时刻操作
ItemDecoration 那个分水岭对象的。

onDraw 中:

 @Override public void onDraw { super.onDraw; final int count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get.onDraw(c, this, mState); } }

draw 方法中:

 @Override public void draw { super.draw; final int count = mItemDecorations.size(); for (int i = 0; i < count; i++) { mItemDecorations.get.onDrawOver(c, this, mState); } }

能够见见在 View 的如上多少个法子中,分别调用了 ItemDecoration 对象的
onDraw onDrawOver 方法。

那七个抽象方法,由大家承接 ItemDecoration 来和睦完成,他们分别就是
onDraw 在 item view 绘制在此之前调用,onDrawOver 在 item view
绘制之后调用。

进而绘制顺序就是 Decoration 的 onDraw,ItemView的 onDraw,Decoration 的
onDrawOver。

(好像越写越来越多…收不住了…)

我们在 onDrawonDrawOver 方法中就能够绘制 drawable
对象了。此时分水岭就显现出来了。还记得刚才的 mInsetsDirty
字段吗?在累加分水岭的时候,无论是 RecyclerView 子 View,依然缓存中的
View,其 LayoutParams 中的 mInsetsDirty 属性,都被置为 true。
大家来解释一下这么些字段的机能:

 Rect getItemDecorInsetsForChild(View child) { final LayoutParams lp = (LayoutParams) child.getLayoutParams(); if (!lp.mInsetsDirty) { // 当 mInsetsDirty 为 false,说明 mDecorInsets 缓存可用 return lp.mDecorInsets; } if (mState.isPreLayout() && (lp.isItemChanged() || lp.isViewInvalid { // changed/invalid items should not be updated until they are rebound. return lp.mDecorInsets; } final Rect insets = lp.mDecorInsets; insets.set(0, 0, 0, 0); final int decorCount = mItemDecorations.size(); for (int i = 0; i < decorCount; i++) { mTempRect.set(0, 0, 0, 0); mItemDecorations.get.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; }

来解释一下这段代码,首先 getItemDecorInsetsForChild 方法是在
RecyclerView 实行 measureChild 时调用的。目标就是为了收取 RecyclerView
的 ChildView 中的分水岭属性 — 在 LayoutParams 中缓存的 mDecorInsets
。而 mDecorInsets 就是 Rect 对象,
其保存记录的是怀有增添分水岭须求的上空累加的总额,由分界线的
getItemOffsets 方法影响。

末段在 measureChild 方法里,将分水岭 ItemDecoration 的尺码参预到
itemView 的 padding 中。

唯独我们都知道缓存并非三翻五次可用的,mInsetsDirty 那些 boolean
字段来记录它的时效性,当 mInsetsDirty 为 false
时,表达缓存可用,直接抽取可以,当 mInsetsDirty 为 true
时,表达缓存的分水岭属性就需求重新计算了。

到此,关于 RecyclerView 增多分界线 ItemDecoration
的源码深入分析,也就大旨甘休了。

设若有人问笔者,在哪些情形下您相对会挑选 RecyclerView,实际不是ListView?即使要求对 Item 的卡通有早晚供给,这纯属是本身选用 RecyclerView
的机要原因之一。ListView 假设要做 Item 的增加和删除动画,那可要费很大劲儿,而
RecyclerView 自个儿对动画片就有很好的支撑。

 public void setItemAnimator(ItemAnimator animator) { if (mItemAnimator != null) { mItemAnimator.endAnimations(); mItemAnimator.setListener; } mItemAnimator = animator; if (mItemAnimator != null) { mItemAnimator.setListener(mItemAnimatorListener); } }

其一措施正是为 RecyclerView 设置动画的进口,逻辑正是排除旧的
Listener,设置新的 Listener。这几个没什么可多说的,大家此次直接来拜谒ItemAnimator 这么些类。

当本人第三遍粗略的看 RecyclerView
的源码之时,以为那也太多了呢,30000贰仟多行,那得多复杂啊。后来再看看,才知晓,RecyclerView
是把

ItemAnimator 、LayoutManager
等等这么些模块都当在那之中类写在了一块。。。作者不知道这么设计的目标是吗,除了写着方便之外大概找不到任何理由了,so…
可能是 google 的技师偷个懒吧 – –

来看 ItemAnimator 类:

 public @NonNull ItemHolderInfo recordPreLayoutInformation(@NonNull State state, @NonNull ViewHolder viewHolder, @AdapterChanges int changeFlags, @NonNull List<Object> payloads) { return obtainHolderInfo().setFrom(viewHolder); } public @NonNull ItemHolderInfo recordPostLayoutInformation(@NonNull State state, @NonNull ViewHolder viewHolder) { return obtainHolderInfo().setFrom(viewHolder); }

那多个函数看命名也能猜三个大概,其目标便是为了记录在 RecyclerView
布局以前/之后,要求的一对 layout 消息保存在 ItemHolderInfo
中,ItemHolderInfo 那一个类就是用来记录当前 ItemView 的职位音讯

recordPreLayoutInformation 来记录 layout 在此以前的事态音信,这些核心在
dispatchLayoutStep1 之中调用。

dispatchLayoutStep1那么些方法做了怎么吗,来走访注释就了然于目:

 /** * The first step of a layout where we; * - process adapter updates * - decide which animation should run * - save information about current views * - If necessary, run predictive layout and save its information */

这是布局的首先步:实行 adapter 布局的翻新,决定实践哪个动画,保存当前
view 的音信,借使有至关重要,运维 predictive layout。

相同的 recordPostLayoutInformation 方法来记录 layout
进程实现时,ItemView 的音讯。

它在 dispatchLayoutStep3 方法中调用,dispatchLayoutStep3 方法效果:

 /** * The final step of the layout where we save the information about views for animations, * trigger animations and do any necessary cleanup. */

layout 的末梢一个手续,保存 view
动画的消息,实施动画,状态清理。当然也可能有 dispatchLayoutStep2
方法,他们多个办法依次在 onLayout 方法的 dispatchLayout办法调用。

dispatchLayout 方法目标是 layout RecyclerView 的
childview,并且记录动画执行的历程、更动。

前段时间清楚了那三个 API 的意义正是在 layout 前后记录 itemview
动画的动静,保存在 ItemHolderInfo 中,大家接二连三查找施行动画的 API。

  • animateDisappearance当 ViewHolder 从 RecyclerView 的 layout
    中移除时,调用
  • animateAppearance当 ViewHolder 添加进 RecyclerView 时,调用
  • animatePersistence当 ViewHolder 已经增加进 layout 还未移除时,调用
  • animateChange当 ViewHolder 已经增添进 layout 还未移除,何况调用了
    notifyDataSetChanged 时,调用。

上述多少个 api 就是为 RecyclerView 推行动画的。

调用的机遇和议程是怎样的呢?

 /** * 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); } @Override public void processPersistent(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) { viewHolder.setIsRecyclable; if (mDataSetHasChangedAfterLayout) { // since it was rebound, use change instead as we'll be mapping them from // stable ids. If stable ids were false, we would not be running any // animations if (mItemAnimator.animateChange(viewHolder, viewHolder, preInfo, postInfo)) { postAnimationRunner(); } } else if (mItemAnimator.animatePersistence(viewHolder, preInfo, postInfo)) { postAnimationRunner(); } } @Override public void unused(ViewHolder viewHolder) { mLayout.removeAndRecycleView(viewHolder.itemView, mRecycler); } };

mViewInfoProcessCallback
是三个佚名内部类,在其回调方法中,分别施行了上述四个有关动画的 api。

接轨跟进,看看mViewInfoProcessCallback
那一个接口是何等时候被调用推行的:

 // Step 4: Process view info lists and trigger animations mViewInfoStore.process(mViewInfoProcessCallback);

实践触发方法的职位就在大家刚刚提到的
dispatchLayoutStep3格局中,去依据部分保存的 flag 状态去接触动画。

与其余绑定 adapter 突显数据的控件,比如 ListView、GrideView
相比较,RecyclerView 允许自定义法则去放置子 view,这几个规则的调节者便是LayoutManager。二个 RecyclerView 倘使想突显内容,就不可能不安装一个LayoutManager。

咱俩根据规矩来看设置 LayoutManager 的输入:

 public void setLayoutManager(LayoutManager layout) { if (layout == mLayout) { return; } // 停止滑动 stopScroll(); // TODO We should do this switch a dispatchLayout pass and animate children. There is a good // chance that LayoutManagers will re-use views. if (mLayout != null) { // end all running animations if (mItemAnimator != null) { mItemAnimator.endAnimations(); } // 移除并回收视图 mLayout.removeAndRecycleAllViews(mRecycler); // 回收废弃视图 mLayout.removeAndRecycleScrapInt(mRecycler); mRecycler.clear(); if (mIsAttached) { mLayout.dispatchDetachedFromWindow(this, mRecycler); } mLayout.setRecyclerView; mLayout = null; } else { mRecycler.clear(); } // this is just a defensive measure for faulty item animators. mChildHelper.removeAllViewsUnfiltered(); mLayout = layout; if (layout != null) { if (layout.mRecyclerView != null) { throw new IllegalArgumentException("LayoutManager " + layout + " is already attached to a RecyclerView: " + layout.mRecyclerView); } mLayout.setRecyclerView; if (mIsAttached) { mLayout.dispatchAttachedToWindow; } } mRecycler.updateViewCacheSize(); requestLayout(); }

这段代码主要做了一晃几件事:当此前安装过 LayoutManager
时,移除以前的视图,并缓存视图在 Recycler 中,将新的 mLayout 对象与
RecyclerView 绑定,更新缓存 View 的数据。最终去调用 requestLayout
,重新央求 measure、layout、draw。

关于 RecyclerView 的缓存大家一会再探究,先看看设置的 LayoutManager
是在哪天何地发挥功能的吧:

LayoutManager 作为 RecyclerView
的多个华而不实内部类,大约有贰仟行代码,方法数还相当多,望着头都大了。用单薄的小时去询问每一个艺术的功能显而易见不具体。LayoutManager
的功效便是为 RecyclerView 放置子 view,所以自身直接去牢固 RecyclerView 的
onLayout 和 onMeasure 方法,商讨一下 LayoutManager
的一部分最首要函数的效能。

 @Override protected void onMeasure(int widthSpec, int heightSpec) { if (mLayout == null) { defaultOnMeasure(widthSpec, heightSpec); return; } if (mLayout.mAutoMeasure) { final int widthMode = MeasureSpec.getMode(widthSpec); final int heightMode = MeasureSpec.getMode(heightSpec); final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY; mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec); if (skipMeasure || mAdapter == null) { return; } 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( MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY)); mState.mIsMeasuring = true; dispatchLayoutStep2(); // now we can get the width and height from the children. mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec); } } else { // 省略非自动测量的情况 } }

来分析一下 onMeasure 方法,mAutoMeasure 字段用来标识是不是利用
RecyclerView 的私下认可法规实行机动度量,不然就非得在 LayoutManager
中本身实现 onMeasure 来张开度量。LinearLayoutManager 的 mAutoMeasure
字段属性就被设置成为了 true。

据此大家最重要来看mAutoMeasure为 true 时,衡量的法规。

当 RecyclerView 的 MeasureSpec
MeasureSpec.EXACTLY时,这一年能够直接规定 RecyclerView 的宽高,所以
return 退出衡量。当 RecyclerView 的宽高为不为 EXACTLY
时,首先举行的度量步骤正是
dispatchLayoutStep1,那一个我们在剖判动画源码的时候提到过。dispatchLayoutStep1
的功能总计起来就是记录 layout 此前,view 的音信。

继而继续调用了 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); ... mState.mLayoutStep = State.STEP_ANIMATIONS; }

dispatchLayoutStep2
相当的重大的正是上述代码展现的这两步,onLayoutChildren 那几个函数由
LayoutManager 实现,来规定放置子 view 的算法,寻觅锚点填充
view,锚点的物色和填充 view
的格局,这里就不细说了。因为篇幅实在是太长。具体能够一向去看
LinearLayoutManager 的完结格局。

其次步正是将 mState.mLayoutStep 置为
State.STEP_ANIMATIONS,刚才我们忘记说 mLayoutStep
这一个本性了,从它的命名就掌握它是来标志 layout 那么些进度进行到哪一步了。在
dispatchLayoutStep1 中 mState.mLayoutStep 被置为
State.STEP_LAYOUT,记录 layout 的步骤是怎样原因吧?来拜望 RecyclerView
的 onLayout 方法:

 @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; }

跟进 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; 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; dispatchLayoutStep2(); } else { // always make sure we sync them (to ensure mode is exact) mLayout.setExactMeasureSpecsFrom; } dispatchLayoutStep3(); }

看到 mState.mLayoutStep 的机能了啊,当我们在 onMeasure
方法中一度调用过 dispatchLayoutStep1dispatchLayoutStep2 时,在
onLayout 方法中只会调用
dispatchLayoutStep3dispatchLayoutStep3方法大家在动画部分的源码讲授过。

回想一下,什么意况下会在 onMeasure 方法中从来调用
dispatchLayoutStep1dispatchLayoutStep2 ? 正是 RecyclerView 的
MeasureSpec 不为 EXACTLY 时,那一个状态下 RecyclerView
不能够自个儿分明本身的宽高,只好在衡量、布局了子 view
手艺显著本身的宽高。所以在 onMeasure 的时候就调用了
dispatchLayoutStep1dispatchLayoutStep2 ,在 onLayout 仅仅调用
dispatchLayoutStep3 方法就能够了。

万一文字表达的非常不足明晰,这里来一张图:

图片 15onMeasure/onLayout

末段在那些章节中,总括一下 LayoutManager 的效果与利益:

  • 协助 RecyclerView 完成 onMeasure 过程
  • 透过 onLayoutChildren 实现对子 view 的布局
  • 滚动子视图
  • 滚动进程中判定何时增加 view ,哪一天回收
    view,相当于对缓存机遇的剖断。

那篇小说小编在劳作之余写写停停大约花了七日,终于到了本文的结尾三个模块 —
recycler在上文中一再提到,Recycler 正是决定 RecyclerView
缓存的骨干类。领会了它的行事进度,就足以弄领会 RecyclerView 如何回收
view,怎样复用 view 的。

在上多少个章节,大家说过了 LayoutManager 其实就是 Recycler 的调整者,由
LayoutManager 来支配调用 Recycler 关键办法的火候,口说无凭,就径直来拜候LinearLayoutManager 的代码吧:

首先来看最首要的部分 onLayoutChildren 方法,那一个办法是在 RecyclerView
dispatchLayoutStep2 阶段调用的。

onLayoutChildren 方法比较长,它基本的机能正是搜索二个锚点,为
RecyclerView 填充子 View。在 onLayoutChildren 调用的 fill
方法便是确实初始填充 layout 的措施。

 int fill(RecyclerView.Recycler recycler, LayoutState layoutState, RecyclerView.State state, boolean stopOnFocusable) { // max offset we should set is mFastScroll + available final int start = layoutState.mAvailable; int remainingSpace = layoutState.mAvailable + layoutState.mExtra; LayoutChunkResult layoutChunkResult = mLayoutChunkResult; while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore { layoutChunkResult.resetInternal(); layoutChunk(recycler, state, layoutState, layoutChunkResult); if (layoutChunkResult.mFinished) { break; } if (layoutState.mScrollingOffset != LayoutState.SCROLLING_OFFSET_NaN) { layoutState.mScrollingOffset += layoutChunkResult.mConsumed; if (layoutState.mAvailable < 0) { layoutState.mScrollingOffset += layoutState.mAvailable; } recycleByLayoutState(recycler, layoutState); } } return start - layoutState.mAvailable; }

那正是回顾了有的代码的 fill 方法,当中的 while 循环中,通过判定LaytouState 中保留的情事来持续的通过 LayoutChunk 方法填充
view。所以随着再看 LayoutChunk 方法。

 void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state, LayoutState layoutState, LayoutChunkResult result) { View view = layoutState.next; if (view == null) { // 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; } LayoutParams params = (LayoutParams) view.getLayoutParams(); if (layoutState.mScrapList == null) { if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { addView; } else { addView; } } else { if (mShouldReverseLayout == (layoutState.mLayoutDirection == LayoutState.LAYOUT_START)) { addDisappearingView; } else { addDisappearingView; } } }

以上就是 layoutChunk 的部分代码,能够看来通过 next 方法抽出来 view
,而且经过 addView 增多到 RecyclerView 里面去,继续跟进next
方法,看看它是用怎么着艺术和准绳收取来 View 的。

 View next(RecyclerView.Recycler recycler) { if (mScrapList != null) { return nextViewFromScrapList(); } final View view = recycler.getViewForPosition(mCurrentPosition); mCurrentPosition += mItemDirection; return view; }

联机收看此间,我们的 Recycler 终于现身了…跟进 getViewForPosition
方法吧。

此前能够先看看 Recycler 多少个相当重要的成员变量,便于大家越来越好的认识RecyclerView 的缓存结构:

 final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>(); ArrayList<ViewHolder> mChangedScrap = null; final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>(); private final List<ViewHolder> mUnmodifiableAttachedScrap = Collections.unmodifiableList(mAttachedScrap); RecycledViewPool mRecyclerPool; private ViewCacheExtension mViewCacheExtension;

提早说一下,RecyclerView 其实能够算作是四级缓存、mAttachedScrap
、mCachedViews 、mViewCacheExtension、mRecyclerPool
那多个对象正是当作每一级缓存的布局的。

getViewForPosition 方法:

 View getViewForPosition(int position, boolean dryRun) { return tryGetViewHolderForPositionByDeadline(position, dryRun, FOREVER_NS).itemView; }

跟进
tryGetViewHolderForPositionByDeadline艺术,一段一段的翻阅这些点子代码。在篇章之初大家就说过,RecyclerView
的缓存单元是 ViewHolder,那几个tryGetViewHolderForPositionByDeadline
方法就完整的显示了怎么样在各种层级的缓存中,抽取来
ViewHolder,上面大家一步步的分析一下:

 // 0) If there is a changed scrap, try to find from there if (mState.isPreLayout { holder = getChangedScrapViewForPosition; fromScrapOrHiddenOrCache = holder != null; }

首先步,从 mChangedScrap 中尝试收取缓存的 ViewHolder
,若一纸空文,再次来到空。

 // 1) Find by position from scrap/hidden list/cache if (holder == null) { holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun); if (holder != null) { if (!validateViewHolderForOffsetPosition { // recycle holder (and unscrap if relevant) since it can't be used if  { // 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(); } else if (holder.wasReturnedFromScrap { holder.clearReturnedFromScrapFlag(); } recycleViewHolderInternal; } holder = null; } else { fromScrapOrHiddenOrCache = true; } } }

假若在第一步发掘并未有缓存的 ViewHolder,则去 mAttachedScrap 中取,当然
mAttachedScrap 中从来不咋办呢,接着去 mHiddenViews
里面去找,如果还并未有,继续从 mCachedViews 中取缓存的 ViewHolder
,这一二种操作是缓存层级的第二步。

 if (holder == null) { final int offsetPosition = mAdapterHelper.findPositionOffset; if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount { throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item " + "position " + position + "(offset:" + offsetPosition + ")." + "state:" + mState.getItemCount; } final int type = mAdapter.getItemViewType(offsetPosition); // 2) Find from scrap/cache via stable ids, if exists if (mAdapter.hasStableIds { holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition), type, dryRun); if (holder != null) { // update position holder.mPosition = offsetPosition; fromScrapOrHiddenOrCache = true; } } 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; if (holder == null) { throw new IllegalArgumentException("getViewForPositionAndType returned" + " a view which does not have a ViewHolder"); } else if (holder.shouldIgnore { throw new IllegalArgumentException("getViewForPositionAndType returned" + " a view that is ignored. You must call stopIgnoring before" + " returning this view."); } } } if (holder == null) { // fallback to pool if  { Log.d(TAG, "tryGetViewHolderForPositionByDeadline(" + position + ") fetching from shared pool"); } holder = getRecycledViewPool().getRecycledView; if (holder != null) { holder.resetInternal(); if (FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt; } } } if (holder == null) { long start = getNanoTime(); if (deadlineNs != FOREVER_NS && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) { // abort - we have a deadline we can't meet return null; } holder = mAdapter.createViewHolder(RecyclerView.this, type); if (ALLOW_THREAD_GAP_WORK) { // only bother finding nested RV if prefetching RecyclerView innerView = findNestedRecyclerView(holder.itemView); if (innerView != null) { holder.mNestedRecyclerView = new WeakReference<>(innerView); } } long end = getNanoTime(); mRecyclerPool.factorInCreateTime(type, end - start); if  { Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder"); } } }

getScrapOrCachedViewForId 方法中,依据 id 依次在 mAttachedScrap
、mCachedViews 集合中找出缓存的 ViewHolder,假诺都不设有,则在
ViewCacheExtension 对象中搜寻缓存,ViewCacheExtension
这几个类须要使用者通过 setViewCacheExtension 方法传入,RecyclerView
自己并不会促成它,平时符合规律的施用也用不到。缓存层级的第三步,大家就深入分析完成了,来看最后一步。

 if (holder == null) { // fallback to pool if  { Log.d(TAG, "tryGetViewHolderForPositionByDeadline(" + position + ") fetching from shared pool"); } holder = getRecycledViewPool().getRecycledView; if (holder != null) { holder.resetInternal(); if (FORCE_INVALIDATE_DISPLAY_LIST) { invalidateDisplayListInt; } } }

这段代码是从 RecycledViewPool 中取依据 type 取 ViewHolder,对于
RecycledViewPool 上边多说几句:

 static class ScrapData { ArrayList<ViewHolder> mScrapHeap = new ArrayList<>(); int mMaxScrap = DEFAULT_MAX_SCRAP; long mCreateRunningAverageNs = 0; long mBindRunningAverageNs = 0; } SparseArray<ScrapData> mScrap = new SparseArray<>();

从那边大家知道,RecycledViewPool 其实是二个 SparseArray 保存 ScrapData
对象的布局。依据 type 缓存 ViewHolder,每一个 type,私下认可最多保留5个
ViewHolder。上边提到的 mCachedViews 那些集结暗中同意最大值是 2 。

RecycledViewPool 能够由八个 ReyclerView 共用。

RecycledViewPool 正是缓存结构中的第四级缓存了,假设 RecycledViewPool
中如故未有缓存的 ViewHolder 如何是好呢?

 if (holder == null) { long start = getNanoTime(); if (deadlineNs != FOREVER_NS && !mRecyclerPool.willCreateInTime(type, start, deadlineNs)) { // abort - we have a deadline we can't meet return null; } holder = mAdapter.createViewHolder(RecyclerView.this, type); if (ALLOW_THREAD_GAP_WORK) { // only bother finding nested RV if prefetching RecyclerView innerView = findNestedRecyclerView(holder.itemView); if (innerView != null) { holder.mNestedRecyclerView = new WeakReference<>(innerView); } } long end = getNanoTime(); mRecyclerPool.factorInCreateTime(type, end - start); if  { Log.d(TAG, "tryGetViewHolderForPositionByDeadline created new ViewHolder"); } }

本条时候从不艺术,就只好调用
mAdapter.createViewHolder(RecyclerView.this, type),来成立三个ViewHolder 了。

到此我们就驾驭一个 ViewHolder 是何许层层从缓存中抽出的了。

写在背后:正文从源码的角度斟酌了 RecyclerView 的最重要模块和效应,但是RecyclerView
自己是很复杂的,要思念到丰富多的气象,光是各样景况的记录就令人看得很迷,这种复杂度的控件真不是形似程序猿可以消除的。然而大家不要深究每一块细节,将差非常的少的流程梳理在心,也自然会有获取和启迪。今后计划总括下
RecyclerView 大概爆发的有个别复用难题。有毛病可以在凡尘调换。

发表评论

电子邮件地址不会被公开。 必填项已用*标注