一.基本介绍
在平时的开发中我们或多或少地接触过ListView,所以我们对ListView并不陌生。不过ListView存在某些缺点,所以谷歌又在ListView的基础上推出了RecyclerView。记得RecyclerView刚推出时,网上许多文章都介绍了它的优点,甚至有人说RecyclerView可以完全取代ListView,我觉得这个说法太绝对了,虽然RecyclerView在ListView的基础上优化了,但是实际应用中要使用哪个控件,还是要根据实际情况来看的。接下来我们从源码的角度了解RecyclerView,看看其与ListView的而区别。
二.源码解析
在看RecyclerView源码中的方法之前,我们有必要先看看RecyclerView源码中的内部类,这将更好地帮助我们看懂源码。所以我们先来看看其内部类。
·LayoutManager
LayoutManager 负责测量和摆放 item view,可以帮助我们实现普通的竖直,水平,网格等列表,在ListView中不存在这个内部类,所以使用ListView时,我们一般只能实现竖直方向列表。
在这个内部类中的大部分方法是实现测量child的大小,我们这里不展开说。我们只说其中的一个方法,之所以说这个方法是因为我第一次看的时候,理解错了,之后再看的时候虽然知道了实际的用途,但是却存在疑问,到现在我也不太理解为什么要这么设置。先来看看这个方法吧。
public void setAutoMeasureEnabled(boolean enabled) {
mAutoMeasure = enabled;
}
这个方法很简单,只是设置mAutoMeasure的值,而mAutoMeasure只是用来标记是否自动测量(将AutoMeasure直接翻译为自动测量了)。如果是自动测量,那么就将所有的测量工作都交给LayoutManager来做,这好像就是本来设置LayoutManager的目的。而如果mAutoMeasure值为false,也就是不是自动测量了,根据源码里面的说明,此时就应该由RecyclerView自己对child进行测量了。但是仔细阅读源码和理解之后,发现无论是不是自动测量,最终测量都是由LayoutManager来做。
上面所说的问题也是我存在疑惑的地方,看起来mAutoMeasure这个变量并不是必需的,因为本来layoutManager的职责就包括测量item view。
所以请有理解这个设计意图的读者能帮我解惑,或者我上面的理解有错的地方,恳请指正。
·Adapter
说起Adapter,我们都不陌生,因为在ListView里面我们就有使用到Adapter,不过,RecyclerView对Adapter进行了改进。
在ListView中,我们可能会在Adapter写一个内部类ViewHolder,减少不必要的findViewById操作。当然我们能自己选择要不要做这一步优化。但是在RecyclerView中的Adapter源码中存在下面的方法。
public final VH createViewHolder(ViewGroup parent, int viewType) {
TraceCompat.beginSection(TRACE_CREATE_VIEW_TAG);
final VH holder = onCreateViewHolder(parent, viewType);
holder.mItemViewType = viewType;
TraceCompat.endSection();
return holder;
}
从这里看出RecyclerView强制我们必须使用ViewHolder,这样做有什么好处呢?ViewHolder缓存了相关的View,当我们设置数据时,可以重复利用这些已经缓存的子View去显示不同item的数据,避免不必要的findViewById操作。
·Recycler
Recycler主要管理废弃或分离的item的复用。这个类主要和LayoutManager配合使用。
接下来介绍这个类中几个关键的变量和方法。
//与RecyclerView绑定,但是已经标记了要移除或复用的ViewHolder列表
ArrayList<ViewHolder> mAttachedScrap;
//与RecyclerView分离的ViewHolder列表
ArrayList<ViewHolder> mChangedScrap;
//ViewHolder缓存列表
ArrayList<ViewHolder> mCachedViews;
//提供复用ViewHolder池
RecycledViewPool mRecyclerPool;
//开发者控制的ViewHolder缓存
ViewCacheExtension mViewCacheExtension;
public View getViewForPosition(int position) {
return getViewForPosition(position, false);
}
getViewForPosition(int):获取某个位置需要展示的View,先检查是否有可复用的View,如果没有就创建新的View并返回。
上面的具体过程如下:
a.检查mChangedScrap,若匹配到则返回相应的ViewHolder。
b.检查mAttachedScrap,操作同上。
c.检查mViewCacheExtension,操作同上。
d.检查mRecycledPool,操作同上。
e.如果上述操作都没有成功,则执行Adapter.createViewHolder(),新建ViewHolder实例。
f.返回holder.itemView。
为什么会按照上面次序检查呢?这涉及到RecyclerView中的缓存。一般系统预先提供了两级缓存,如果有特殊需求,可实现三级缓存。这三级缓存如下:
·第一级缓存:这级缓存主要用到的存储容器有mCacheViews,mAttachedScrap和mChangedScrap。
·第二级缓存:这级缓存不是系统预先提供的,而是利用ViewCacheExtension充当附加的缓存池,当RecyclerView从第一级缓存找不到需要的View时,将会从ViewCacheExtension中找,这个缓存时由开发者维护的,如果开发者不显示设置它,则默认不会启用。只有当我们有特殊需求时(例如在调用系统的缓存池前返回一个特定的视图),才会使用这级缓存。
·第三级缓存:这级的缓存主要用到最强大的缓存器RecycledViewPool。
·RecycledViewPool
上面说到RecycledViewPool是最强大的缓存器,之所以这么说是因为RecycledViewPool使我们能在RecycledView之间共享ViewHolder,所以其一般能绑定多个Adapter。
说完RecycledView中的内部类,接下来说说其内部的方法。RecycledView是一个控件,所以我们还是按照View的绘制流程来看RecycledView的源码。
·onMeasure
protected void onMeasure(int widthSpec, int heightSpec) {
//如果LayoutManager为空则先初始化
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.mAutoMeasure) {
...
//如果RecycledView是第一次进行测量则调用 dispatchLayoutStep1()
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1();
}
...
dispatchLayoutStep2();
//只要RecycledView中有一个child无法得到确切的宽高,就要重新测量
if (mLayout.shouldMeasureTwice()) {
...
dispatchLayoutStep2();
}
} else {
if (mHasFixedSize) {
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
return;
}
...
}
}
上面只贴出了onMeasure的部分代码,从上面我们可以看出在测量过程中会调用dispatchLayoutStep1()和dispatchLayoutStep2()方法,所以接下来我们分别看看这两个方法。
/**
根据源码的说明,我们可以知道这个方法会找出那些不需要移除的child,然后预先设置它们的位置
*/
private void dispatchLayoutStep1() {
...
if (mState.mRunSimpleAnimations) {
int count = mChildHelper.getChildCount();
//遍历找出不需要移除的child
for (int i = 0; i < count; ++i) {
....
}
}
//预先设置不需要移除的child的位置
if (mState.mRunPredictiveAnimations) {
...
mLayout.onLayoutChildren(mRecycler, mState);
...
for (int i = 0; i < mChildHelper.getChildCount(); ++i) {
final View child = mChildHelper.getChildAt(i);
final ViewHolder viewHolder = getChildViewHolderInt(child);
if (viewHolder.shouldIgnore()) {
continue;
}
if (!mViewInfoStore.isInPreLayout(viewHolder)) {
int flags = ItemAnimator.buildAdapterChangeFlagsForAnimations(viewHolder);
boolean wasHidden = viewHolder
.hasAnyOfTheFlags(ViewHolder.FLAG_BOUNCED_FROM_HIDDEN_LIST);
if (!wasHidden) {
flags |= ItemAnimator.FLAG_APPEARED_IN_PRE_LAYOUT;
}
final ItemHolderInfo animationInfo = mItemAnimator.recordPreLayoutInformation(
mState, viewHolder, flags, viewHolder.getUnmodifiedPayloads());
if (wasHidden) {
recordAnimationInfoIfBouncedHiddenView(viewHolder, animationInfo);
} else {
mViewInfoStore.addToAppearedInPreLayoutHolders(viewHolder, animationInfo);
}
}
}
...
} else {
clearOldPositions();
}
...
}
/**
在这个方法里面才真正根据View测得的状态去设置其位置,这个方法可能会被多次调用(比如当Adapter中的数据发生改变),不过经过这个方法
之后,View的宽高完全确定下来了。
*/
private void dispatchLayoutStep2() {
eatRequestLayout();
onEnterLayoutOrScroll();
mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
mAdapterHelper.consumeUpdatesInOnePass();
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
// Step 2: Run layout
mState.mInPreLayout = false;
mLayout.onLayoutChildren(mRecycler, mState);
mState.mStructureChanged = false;
mPendingSavedState = null;
// onLayoutChildren may have caused client code to disable item animations; re-check
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
mState.mLayoutStep = State.STEP_ANIMATIONS;
onExitLayoutOrScroll();
resumeRequestLayout(false);
}
·onLayout():在onLayout中会调用dispatchLayout()方法, dispatchLayout()实际就像layoutChildren()的封装,在这个方法完成对各个child位置的设置。具体实现还是调用了上面所说的dispatchLayoutStep1()和dispatchLayoutStep2()。在确保了这两个方法已经调用之后,还会调用dispatchLayoutStep3()。
dispatchLayoutStep3()主要是保存所有View相关的动画信息,然后触发动画和做一些必要的清理。
·onDraw():只是遍历了所有的child并调用他们的onDraw()方法。
关于RecycledView的部分源码介绍完了,通过学习源码和日常开发的使用,我们都能发现RecycledView和ListView的区别,虽然RecycledView比ListView性能好了一些,在布局方面也灵活许多,但个人觉得RecycledView在使用上比ListView麻烦了一些。所以实际开发中选择哪一个并不是绝对的,根据实际选择合适的就行了,也不用一昧的抬高RecycledView的地位。