- 首先看一下为何说RecycleView(主要有适配器模式,观察者模式adapter通知更新UI)中的adapter是个适配器模式!
适配器模式
- 在使用recycleView时最常用的就是adapter,也叫作适配器模式,那我们看看都有哪些吧
类适配器
- 类适配器通过集成与adaptee也就是继承关系,并且实现target方法,重写target方法中调用adaptee的方法,这个是静态的方式
- 而对象适配器通过委派与adaptee即持有adaptee对象,是动态的方式
- 类适配器可以重定义实现行为,而对象适配器重定义适配的行为比较困难,但是添加行为较方便。
public interface Target {
public void request();
}
public class Adaptee {
public void specialRequest(){
System.out.println("被适配类 具有特殊功能...");
}
}
//类继承Adaptee并实现接口,然后通过接口方法调取adaptee的方法即可
public class AdapterInfo extends Adaptee implements Target {
public static void main(String[] args) {
Target adaptee = new AdapterInfo();
adaptee.request();
}
@Override
public void request() {
super.specialRequest();
}
}
对象适配器: 使用较多
class Adapter implements Target{
// 直接关联被适配类
private Adaptee adaptee;
// 可以通过构造函数传入具体需要适配的被适配类对象
public Adapter (Adaptee adaptee) {
this.adaptee = adaptee;
}
public void request() {
// 这里是使用委托的方式完成特殊功能
this.adaptee.specificRequest();
}
}
- 类跟对象适配器区别:类适配器模式需要自身来创建一个adaptee,对象适配器模式可以直接使用一个已有的adaptee实例来转换接口
RecycleView中的adapter
- 要想搞明白他的适配器模式,我们必须的看他的继承关系
//RecycleView的类继承关系
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2
//adapter的继承关系
class FooterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder>
//Adapter
public abstract static class Adapter<VH extends RecyclerView.ViewHolder> {
private final RecyclerView.AdapterDataObservable mObservable = new RecyclerView.AdapterDataObservable();
private boolean mHasStableIds = false;
public Adapter() {
}
//mObservable是每一个Adapter创建一个内部类,观察者模式监听条目的变化
static class AdapterDataObservable extends Observable<RecyclerView.AdapterDataObserver> {
AdapterDataObservable() {
}
- 通过以上分析,我们使用了RecycleView.Adapter是一个抽象类定义的适配器,这个抽象类的方法要提供给RecycleView内部使用的,因此实现一个FootAdapter的实现类,然后传递给RecycleView.setAdapter(adapter)就是一个对象适配器
- 而且对象适配器是可以随时更换的,我们在RecycleView中也是可以更换Adapter的具体实现类从而根据不同的类型加载不同的Adapter实现不同的界面!
- 查看setAdapter的创建
public abstract static class Adapter<VH extends ViewHolder> {
//静态类用于Adapter数据变化的Observable,每一个类都有一个自己的Observable对象
private final AdapterDataObservable mObservable = new AdapterDataObservable();
//setAdapter
public void setAdapter(Adapter adapter) {
// bail out if layout is frozen
setLayoutFrozen(false);
setAdapterInternal(adapter, false, true);
requestLayout();
}
//setAdapterInternal:使用心得adapter替换掉老的adapter,并且触发监听mObserver
private void setAdapterInternal(@Nullable Adapter adapter, boolean compatibleWithPrevious,
boolean removeAndRecycleViews) {
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver); // 注销老的adapter观察者
mAdapter.onDetachedFromRecyclerView(this); //解绑recycleview
}
....
mAdapterHelper.reset();
final Adapter oldAdapter = mAdapter;
mAdapter = adapter;
if (adapter != null) {
adapter.registerAdapterDataObserver(mObserver); //注册观察者
adapter.onAttachedToRecyclerView(this);
}
...
}
RecycleView的缓存
- 之所以使用RecycleView替换掉ListView是因为其优秀的缓存机制
ListView缓存
- ListView只有两级缓存,其是RecycleBin缓存机制
- mActiveViews :一级是缓存屏幕上可见的view
- mScrapViews :二级缓存ListView中所有废弃的,这是一个ArrayList<View>[] 数组,每一种type对应一个自己的arrayList缓存
- 区别: RecycleView缓存的是ViewHolder,而list只是缓存View,需要自己构建一个ViewHolder对象
四级缓存
- 一级缓存: mAttachedScrap 和mChangedScrap :优先级最高的缓存,RecycleView在获取viewHolder时,优先会从这两个缓存中找,其中前者存储的是当前还在屏幕中的viewHolder,后者存储的是数据被更新的viewHolder,比如调用了adapter.notifyItemChanged()方法更新条目
- mAttachedScrap :他表示存储在当前还在屏幕中的ViewHolder,实际上是从屏幕上分离出来的ViewHolder,但是又即将添加到屏幕上去的ViewHolder,比如上下滑动,滑出一个新的Item,此时会重新调用LayoutManager的onLayoutChildren方法,从而会将屏幕上所有的ViewHolder先scrap掉(废弃掉),添加到mAttachedScrap中去,然后在重新布局每一个ItemView时,会优先从mAttachedScrap中获取,这样效率就会非常的高,这个过程不会重新onBindViewHolder
- 二级缓存: mCachedViews : 默认大小为2,通常用来存储预取的viewHolder,同时在回收ViewHolder时,也会可能存储一部分的viewHolder,这部分的viewHolder通常同一级缓存差不太多,下面分析
- 默认是2,不过通常是3,3由默认的大小2+ 预取的个数1,所以在RecycleView在首次加载时,mCacheView的size为3(LinerLayoutManager布局为例)
- 三级缓存:ViewCachedExtension :自定义缓存,通常用不到
- RecycleViewPool :根据viewType来缓存ViewHolder,每个viewType的数组大小为5,可以动态改变
ViewHolder的几个状态值
- 通过阅读RecycleView的源码时,到处看到ViewHolder的isInvalid ,isRemoved ,isBound, isTmpDetached, isScrap , isUpdated 这几个方法,其实是个状态机
- isInvalid : flag_invalid : 表示当前ViewHolder是否已经失效,通常由三种情况会出现这种状况
- 调用了adapter.notifyDataSetChanged()方法
- 手动调用RecycleView.invalidateItemDecorations()方法
- 调用RecycleView的setAdapter方法或swapAdapter()方法
- isRemoved : Flag_removed : 表示当前ViewHolder是否被移除, 通常来说,数据源被移除了部分数据,然后调用了adapter.notifyItemRemoved()
public void removeItem(int position){
data.remove(posiiton);
notifyItemRemoved(position);
notifyItemRangeChanged(position, data.size() - position); //不加这个position位置会错乱的.onBindViewHolder也不会调用
}
- isBound: flag_bound : 表示当前ViewHolder是否已经调用了onBindViewHolder
- isTmpdetached : flag_tmp_detached : 表示当前的ItemView是否从RecycleView(父View)detach掉,通常由两种情况:
- 手动调用了RecycleView.detachView相关方法
public void detachViewFromParent(int offset) {
final View view = getChildAt(offset);
if (view != null) {
final ViewHolder vh = getChildViewHolderInt(view);
if (vh != null) {
if (vh.isTmpDetached() && !vh.shouldIgnore()) {
throw new IllegalArgumentException("called detach on an already"
+ " detached child " + vh + exceptionLabel());
}
if (DEBUG) {
Log.d(TAG, "tmpDetach " + vh);
}
vh.addFlags(ViewHolder.FLAG_TMP_DETACHED); //detachView(View view)调用的回调,设置Flag
}
}
RecyclerView.this.detachViewFromParent(offset);
}
- 从mHideViews中获取viewHolder,会先detach掉这个ViewHolder关联的ItemView
- isScrap : 无flag状态,用mScrapContainer是否为null来判断:表示是否在MAttachedScrap或mChangeScrap数组里面,进而表示当前ViewHolder是否被废弃掉
- isUpdated: flag_update : 表示当前ViewHolder是否已经更新,通常由三种情况:
- isInvalid 方法存在的三种情况
- 调用了adapter.onBindviewHolder方法
- 调用了adapter.notifyItemChanged方法
- 再上方我们有一个mHiddenViews,并不算入四级缓存中,因为其只在动画期间才会有元素,当动画结束后就清空了,自然不能算到四级缓存中
- 上方一级缓存中有两个,即如果调用了adapter.notifyItemChanged(),会重新回调到LayoutManager的onLayoutChildren()中,那么他两又有啥区别呢? 我们不妨看一段代码逻辑:
//根据viewHolder的flag状态决定是放入mAttachedScrap还是mChangedScrap中去
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) //同时标记remove和invalid
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
throw new IllegalArgumentException("Called scrap view with an invalid view."
+ " Invalid views cannot be reused from scrap, they should rebound from"
+ " recycler pool." + exceptionLabel());
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,
viewHolder.getUnmodifiedPayloads());
}
- 则mAttachedScrap里放着两种状态的ViewHolder:
- 被同时标记为remove和invalid
- 完全没有改变的viewHolder
- 这个跟RecyclerView的ItemAnimator有关,如果ItemAnimator为空或者ItemAnimator的canReuseUpdatedViewHolder方法为true,也会放入到mAttachedScrap。那正常情况下,什么情况返回为true呢?从SimpleItemAnimator的源码可以看出来,当ViewHolder的isInvalid方法返回为true时,会放入到 mAttachedScrap里面。也就是说,如果ViewHolder失效了,也会放到mAttachedScrap里面。
- MchangedScrap 里面放的是isUpdated的返回true的情况,即调用了adapter.notifyItemChanged()并且RecycleView的ItemAnimator不为空时加入
- 同时看一下mAttachedScrap / mChangedScrap 的scrap数组和mHiddenViews的区别
- mHiddenViews只存放动画的ViewHolder,动画结束自然就清空了,用于动画期间进行复用的可能性
- scrap数组和mHiddenViews两者不冲突,可能都存在相同的Viewholder,但是这并不影响,动画结束时会从mHiddenViews中移除的
RecycleView的复用
- 通过上面的分析只是说明了四级缓存的性质,但是并没有说明他的复用原理;我们从LayoutState.next()方法开始,我们知道RecycleView是交给LayoutManager在布局itemView时,需要获取一个ViewHolder对象,通过这个方法来获取的,自然复用逻辑也是在这里的
- next() 调用规则: RecycleView中的重写View的scrollby ->scrollByInternal() -> mLayout.scrollHorizontallyBy()/mLayout.scrollVerticallyBy()-> LayoutManager中的->scrollBy() ->fill() ->next() 滑动时调用
//我们以 LinearLayoutManager.next为例
View next(Recycler recycler) {
if (this.mScrapList != null) {
return this.nextViewFromScrapList();
} else {
View view = recycler.getViewForPosition(this.mCurrentPosition);
this.mCurrentPosition += this.mItemDirection;
return view;
}
}
- 可以看到LinerLayoutManager中通过next获取recycleView的下一个viewHolder对象,调用recycleView.getViewForPosition 最终调用的是 tryGetViewHolderForPositionByDeadline() ,这个才是真正复用的核心
- 通过position方式获取对应的viewHolder:优先级比较高,因为每个viewHolder还未被该变,都是由于某一个itemView对应的viewHolder被更新导致的,所以在屏幕上其他viewHolder可以快速对应原来的ItemView
if (position >= 0 && position < RecyclerView.this.mState.getItemCount()) { //position合法
boolean fromScrapOrHiddenOrCache = false;
RecyclerView.ViewHolder holder = null;
if (RecyclerView.this.mState.isPreLayout()) {
holder = this.getChangedScrapViewForPosition(position); //从mChangedScrap中获取
fromScrapOrHiddenOrCache = holder != null;
}
if (holder == null) {
holder = this.getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!this.validateViewHolderForOffsetPosition(holder)) { //检查当前position是否位置匹配,如果不匹配说明滑出去或者失效了
if (!dryRun) {
holder.addFlags(4);
if (holder.isScrap()) {
RecyclerView.this.removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
//两种情况,如果是滑出屏幕说明可服用,加入到mCacheViews中直接复用,如果失效则说明数据不可用,但视图可用,直接回收ViewHolder加入到viewPool中即可,每次从他里面加载都会重新走onBindViewHolder
this.recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
- 以上源码分为两步:
- 从mChangedScrap 中获取ViewHolder,这里面存放的是更新的ViewHolder
- 分别从mAttachedScrap,mHiddenViews,mCachedViews获取ViewHolder
- 首先看从mChangedScrap中获取:如果当前是预布局阶段,则从mChangedScrap中获取ViewHolder
- 预布局:preLayout,即当前RecycleView处于dispatchLayoutStep1阶段时,称为预布局
- dispatchLayoutStep2阶段被称为真正的布局阶段
- dispatchLayoutStep3称为postLayout阶段
- 同时要想真正开启预布局,必须要有itemAnimator,并且每个recycleView对应的LayoutManager必须开启预处理动画
- 只有当ItemAnimator不为空,被changed的ViewHolder会放在mChangedScrap数组里面。因为chang动画前后相同位置上的ViewHolder是不同的,所以当预布局时,从mChangedScrap缓存里面去,而正式布局时,不会从mChangedScrap缓存里面去,这就保证了动画前后相同位置上是不同的ViewHolder
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
fromScrapOrHiddenOrCache = holder != null;
}
if (holder == null) {
holder = this.getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
if (holder != null) {
if (!this.validateViewHolderForOffsetPosition(holder)) {
if (!dryRun) {
holder.addFlags(4);
if (holder.isScrap()) {
RecyclerView.this.removeDetachedView(holder.itemView, false);
holder.unScrap();
} else if (holder.wasReturnedFromScrap()) {
holder.clearReturnedFromScrapFlag();
}
this.recycleViewHolderInternal(holder);
}
holder = null;
} else {
fromScrapOrHiddenOrCache = true;
}
}
}
- 分别从mAttachedScrap. mHiddenViews,mCachedViews获取ViewHolder,但是还得判断ViewHolder是否有效,如果无效需要做一些清理操作从一级缓存中remove,然后重新放入到mCacheViews或者RecycleViewPool缓存里面
- 因此从一级,二级缓存中取值,其ViewHolder的原本数据信息都在,直接添加到RecycleView中显示,不需要重新走onBindViewHolder()方法赋值了,只有原来的子项可以重新复用,新建的子项无法从该两级缓存中取值的
- 而mREcycleViewPool:源码中只是缓存ViewHolder并重置数据,相当于一个新建的ViewHolder,只是不走onCreateView()方法,使用时需要重新调用onBindViewHolder()来绑定数据
- 首先看从各种缓存中取值
final ArrayList<RecyclerView.ViewHolder> mAttachedScrap = new ArrayList();
final ArrayList<RecyclerView.ViewHolder> mCachedViews = new ArrayList(); //缓存大小有默认的2加上layoutManager的设置决定
void updateViewCacheSize() {
int extraCache = RecyclerView.this.mLayout != null ? RecyclerView.this.mLayout.mPrefetchMaxCountObserved : 0;
this.mViewCacheMax = this.mRequestedCacheMax + extraCache;
for(int i = this.mCachedViews.size() - 1; i >= 0 && this.mCachedViews.size() > this.mViewCacheMax; --i) {
this.recycleCachedViewAt(i);
}
}
//如果CachedViews中超过了2,则将其移到recycleViewPool缓存中去
void recycleCachedViewAt(int cachedViewIndex) {
RecyclerView.ViewHolder viewHolder = (RecyclerView.ViewHolder)this.mCachedViews.get(cachedViewIndex);
this.addViewHolderToRecycledViewPool(viewHolder, true);
this.mCachedViews.remove(cachedViewIndex);
}
RecyclerView.ViewHolder getScrapOrHiddenOrCachedHolderForPosition(int position, boolean dryRun) {
int scrapCount = this.mAttachedScrap.size();
//从mAttachedScrap中获取,如果可用则返回
int cacheSize;
RecyclerView.ViewHolder vh;
for(cacheSize = 0; cacheSize < scrapCount; ++cacheSize) {
vh = (RecyclerView.ViewHolder)this.mAttachedScrap.get(cacheSize);
if (!vh.wasReturnedFromScrap() && vh.getLayoutPosition() == position && !vh.isInvalid() && (RecyclerView.this.mState.mInPreLayout || !vh.isRemoved())) {
vh.addFlags(32);
return vh;
}
}
if (!dryRun) { //false默认值
//从mHiddrenViews中获取
View view = RecyclerView.this.mChildHelper.findHiddenNonRemovedView(position);
if (view != null) {
vh = RecyclerView.getChildViewHolderInt(view);
RecyclerView.this.mChildHelper.unhide(view);
int layoutIndex = RecyclerView.this.mChildHelper.indexOfChild(view);
if (layoutIndex == -1) {
throw new IllegalStateException("layout index should not be -1 after unhiding a view:" + vh + RecyclerView.this.exceptionLabel());
}
RecyclerView.this.mChildHelper.detachViewFromParent(layoutIndex);
this.scrapView(view);
vh.addFlags(8224);
return vh;
}
}
//从mCacheViews中获取
cacheSize = this.mCachedViews.size();
for(int i = 0; i < cacheSize; ++i) {
RecyclerView.ViewHolder holder = (RecyclerView.ViewHolder)this.mCachedViews.get(i);
if (!holder.isInvalid() && holder.getLayoutPosition() == position) {
if (!dryRun) {
this.mCachedViews.remove(i);
}
return holder;
}
}
return null;
}
- 上面分析的是通过position来获取ViewHolder,我们验证了position合法,则viewType肯定是正确对应的,但是通过viewType获取viewHolder时,position可能失效了,通过viewType可分为三步:
- 如果adapter的hasStableIds = true,优先通过viewType和id两个条件寻找
- 如果未找到,如果adapter.hasStablleIds = false,首先viewCacheExtension里面找,如果还未找到,最后会在RecycleViewPool里面获取ViewHolder
- 如果以上都没有找到合适的ViewHolder,最后会调用adapter的onCreateViewHolder创建一个新的ViewHolder对象
- 首先上面我们只是分析了前两种缓存操作,第三种个人设置的不分析,则第四种 从 RecycleViewPool中获取缓存如何做的呢?首先搞清楚他是啥?
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5;
SparseArray<RecyclerView.RecycledViewPool.ScrapData> mScrap = new SparseArray(); //使用SparseArray键是int型的 viewType 的值,value是ScrapData对象
private int mAttachCount = 0;
//在分析ScrapData是RecycledViewPool静态内部类
static class ScrapData {
final ArrayList<RecyclerView.ViewHolder> mScrapHeap = new ArrayList(); //对于每个viewType的不同保存在不同的ArrayList集合中,相同的viewType保存在同一个集合中,且一种viewType类型最多保存5条ViewHolder
int mMaxScrap = 5;
long mCreateRunningAverageNs = 0L;
long mBindRunningAverageNs = 0L;
ScrapData() {
}
}
int offsetPosition;
int type;
if (holder == null) {
offsetPosition = RecyclerView.this.mAdapterHelper.findPositionOffset(position); //获取position对应的偏移量
if (offsetPosition < 0 || offsetPosition >= RecyclerView.this.mAdapter.getItemCount()) {
//如果当前偏移量没有出现在屏幕上,即更改了数据但是却没有刷新RecycleView时,会报错的,因此我们setData以后一定要notify通知更新UI操作
throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item position " + position + "(offset:" + offsetPosition + ")." + "state:" + RecyclerView.this.mState.getItemCount() + RecyclerView.this.exceptionLabel());
}
type = RecyclerView.this.mAdapter.getItemViewType(offsetPosition); //获取当前位置对应的type
if (RecyclerView.this.mAdapter.hasStableIds()) { //如果从新hasStableIds返回true,即使通过position获取ViewHolder失败,还会尝试通过ViewType去获取ViewHolder,优先在Scrap和cached缓存中寻找
//根据itemId ,type判断当前位置是否存在缓存中,上面只是根据position来判断
holder = this.getScrapOrCachedViewForId(RecyclerView.this.mAdapter.getItemId(offsetPosition), type, dryRun);
if (holder != null) {
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
//用户自定义缓存策略,没啥用
if (holder == null && this.mViewCacheExtension != null) {
View view = this.mViewCacheExtension.getViewForPositionAndType(this, position, type);
if (view != null) {
holder = RecyclerView.this.getChildViewHolder(view);
if (holder == null) {
throw new IllegalArgumentException("getViewForPositionAndType returned a view which does not have a ViewHolder" + RecyclerView.this.exceptionLabel());
}
if (holder.shouldIgnore()) {
throw new IllegalArgumentException("getViewForPositionAndType returned a view that is ignored. You must call stopIgnoring before returning this view." + RecyclerView.this.exceptionLabel());
}
}
}
if (holder == null) {
//根据type从recycleViewPool中获取holder缓存对象
holder = this.getRecycledViewPool().getRecycledView(type); //从五个里面拿数组的最后一个,拿到从缓存数组中移除一个数据
if (holder != null) {
holder.resetInternal(); //只是拿到布局文件,重置里面的信息,以便后续设置值使用
if (RecyclerView.FORCE_INVALIDATE_DISPLAY_LIST) {
this.invalidateDisplayListInt(holder);
}
}
}
if (holder == null) { //如果上方缓存中都没有值了,则调用onCreateViewHolder创建一个新的ViewHolder
long start = RecyclerView.this.getNanoTime();
if (deadlineNs != 9223372036854775807L && !this.mRecyclerPool.willCreateInTime(type, start, deadlineNs)) {
return null;
}
holder = RecyclerView.this.mAdapter.createViewHolder(RecyclerView.this, type); //根据type创建一个新的ViewHolder
if (RecyclerView.ALLOW_THREAD_GAP_WORK) {
RecyclerView innerView = RecyclerView.findNestedRecyclerView(holder.itemView);
if (innerView != null) {
holder.mNestedRecyclerView = new WeakReference(innerView);
}
}
long end = RecyclerView.this.getNanoTime();
this.mRecyclerPool.factorInCreateTime(type, end - start);
}
- 综上即为RecycleView的复用机制,下面我们看一下回收机制
RecycleView回收机制
- 所有的框架一单存在复用,当然首次创建以后肯定会有回收,否则怎么复用呢?同时理解这两个过程,对于我们使用RecycleView及其原理分析会更加清晰明白!
- 首先复习一下复用过程: 回收当然也是一一对应的
- 从scrap数组中查找是否可用,可用直接复用
- 从mCachedViews数组中查找是否可用
- 从mHiddenViews数组中查找,这是一个关于动画的数组
- RecycleViewPool根据type查找是否可用,如果存在则返回布局
- 都没有找到就从adapter.onCreateViewHolder创建一个新的ViewHolder对象返回
- 则对应的回收过程为:
- scrap数组
- mCachedViews数组
- mHiddenViews数组
- RecycleViewPool数组
- scrap数组回收
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID) //这个方法上面已经分析过了
|| !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
if (holder.isInvalid() && !holder.isRemoved() && !mAdapter.hasStableIds()) {
throw new IllegalArgumentException("Called scrap view with an invalid view."
+ " Invalid views cannot be reused from scrap, they should rebound from"
+ " recycler pool." + exceptionLabel());
}
holder.setScrapContainer(this, false);
mAttachedScrap.add(holder);
} else {
if (mChangedScrap == null) {
mChangedScrap = new ArrayList<ViewHolder>();
}
holder.setScrapContainer(this, true);
mChangedScrap.add(holder);
}
}
- 在文章的最开始已经分析过add添加到两个scrap数组中的区别,这里只看scrapView都是那些地方调用的
- getScrapOrHiddenOrCachedHolderForPosition() : 如果从mHiddenViews中获取一个ViewHolder时,会先将这个ViewHolder从mHiddenViews中移除,然后调用scrapView方法将这个ViewHolder放入到scrap数组中
if (!dryRun) {
View view = mChildHelper.findHiddenNonRemovedView(position);
if (view != null) {
// This View is good to be used. We just need to unhide, detach and move to the
// scrap list.
final ViewHolder vh = getChildViewHolderInt(view);
mChildHelper.unhide(view);
int layoutIndex = mChildHelper.indexOfChild(view);
if (layoutIndex == RecyclerView.NO_POSITION) {
throw new IllegalStateException("layout index should not be -1 after "
+ "unhiding a view:" + vh + exceptionLabel());
}
mChildHelper.detachViewFromParent(layoutIndex);
scrapView(view);
vh.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP
| ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
return vh;
}
}
- 在LayoutManager中的scrapOrRecycleView()方法中调用,有两种情况
- 手动调用了LayoutManager相关方法
- RecycleView进行了一次布局(调用了requestLayout方法)
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) {
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
- mCacheViews回收
- mHiddenViews数组
- 一个ViewHolder回收到mHiddenView数组里面的条件比较简单,如果当前操作支持动画,就会调用到RecyclerView的addAnimatingView方法,在这个方法里面会将做动画的那个View添加到mHiddenView数组里面去。通常就是动画期间可以会进行复用,因为mHiddenViews只在动画期间才会有元素。
- RecycleViewPool
- recycleViewPool同mCacheView相同,都是通过recycleViewHolderInternal方法来回收的,只是当不满足mCacheView条件是,即数量大于2时,才会放入到RecycleViewPool中
- 为何说重写hasStableIds方法返回true会提高效率呢?
if (mAdapter.hasStableIds()) { //如果重写了,就尝试根据viewType从一级,二级缓存中去取
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),
type, dryRun);
if (holder != null) {
// update position
holder.mPosition = offsetPosition;
fromScrapOrHiddenOrCache = true;
}
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.shouldIgnore()) {
if (DEBUG) {
Log.d(TAG, "ignoring view " + viewHolder);
}
return;
}
if (viewHolder.isInvalid() && !viewHolder.isRemoved()
&& !mRecyclerView.mAdapter.hasStableIds()) { //如果为true,走下面
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder); //全部回收到了cachedViews和RecycleViewPool中了
} else {
detachViewAt(index);
recycler.scrapView(view); //回收到scrap数组中,一级缓存中
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
数据加载
- 对于上方无论是从缓存中去数据还是新建onCreateView中获取一个ViewHolder,当然需要加载数据了,通过上面分析我们知道,缓存ViewHolder在一级二级缓存中都带有数据的,可以直接复用,但是对于四级或者新建的都相当于重建一个ViewHolder,其中是没有数据的,我们需要绑定走OnBindViewHolder
- 依然是recycleView中的tryGetViewHolderForPositionByDeadline方法中
//bound状态的解释: 该ViewHolder已绑定到一个位置; mPosition,mItemId和mItemViewType均有效。
if (mState.isPreLayout() && holder.isBound()) { //如果当前是bound,就不用在tryBindViewHolderByDeadline-> onBindViewHolder
// do not update unless we absolutely have to.
holder.mPreLayoutPosition = position;
} else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
if (DEBUG && holder.isRemoved()) {
throw new IllegalStateException("Removed holder should be bound and it should"
+ " come here only in pre-layout. Holder: " + holder
+ exceptionLabel());
}
final int offsetPosition = mAdapterHelper.findPositionOffset(position);
//该方法会调用重写的onBindViewHolder
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs);
}
//RecycleViewPool中去ViewHolder调用 resetInternal()重置ViewHolder
void resetInternal() {
mFlags = 0;
...
}
//而上方的判断hold.isBound()
boolean isBound() {
return (mFlags & FLAG_BOUND) != 0; //0 & 任何数 = 0 返回false
}
//因此从viewPool池中和oncreateView重建的都会重新绑定数据,而其他缓存则不用,可以直接复用
- 至此,recycleView布局和加载数据都讲解完成了,布局的加载是委托给LayoutManager实现,而数据是托付给adapter的onBindViewHolder,recycleView只负责对viewHolder的缓存和交互,通过观察者模式对adapter设置Observable,一旦有数据更新则通知view重绘requestLayout
常见问题
- RecycleView 的notifyDataSetChanged导致图片闪烁的原因
- 仅使用notifyDataSetChanged,在重新布局时,会先remove View,然后根据相关情形,缓存到mCachedViews ,ViewCahceExtension(个人实现),RecycleViewPool中在调用LayoutManager.next()方法时,去recycleView中取值复用,如果在pool中都无法取到,则会直接调用oncreateViewHolder重新inflate创建View,导致闪烁
- 使用notifyDataSetChanged() + hasStableIds() true ,+ 复写getItemId() :防止条目重复抖动 或者刷新单个条目,会缓存在mAttachedScrap,因此不会重新createViewHolder