相关文章RecyclerView使用
1.RecyclerView中的一些重要类型:
1.RecyclerView:
public class RecyclerView extends ViewGroup implements ScrollingView, NestedScrollingChild2 {
final Recycler mRecycler = new Recycler();
//处理adapter更新
AdapterHelper mAdapterHelper;
ChildHelper mChildHelper;
Adapter mAdapter;
LayoutManager mLayout;
}
2.Recycler:
Recycler类主要负责实现ViewHolder的缓存:
public final class Recycler {
//只在布局时使用,这个Scrap里面暂存了数据未被修改的viewHolder
final ArrayList<RecyclerView.ViewHolder> mAttachedScrap = new ArrayList();
//只在布局时使用,里面ViewHolder数据发生了变化,需要重新绑定Scrap
ArrayList<RecyclerView.ViewHolder> mChangedScrap = null;
//这个缓存存放已经detatch掉的viewHolder,但是里面的position和viewtype等数据还在
//这个缓存默认大小是2,只在正常滑动屏幕时回收View(而不是重布局)时使用
final ArrayList<RecyclerView.ViewHolder> mCachedViews = new ArrayList();
//这个缓存里面的viewHolder也是detatch过的,而且里面的数据已经清空,
//相当与一个刚new出来的viewHolder,它里面的viewHolder是按照viewtype分类存储的
RecyclerView.RecycledViewPool mRecyclerPool;
//这个对象里面也有一个mHiddenViewList存放隐藏的View
ChildHelper mChildHelper;
}
3.LayoutManager:
这个layoutmanager就是我们平时调用setLayoutManager传进来的LayoutManager,RecyclerView的measure,layout,draw过程都外包给这个类了
public abstract static class LayoutManager {
//这个ChildHelper是RecyclerView直接赋值过来的,二者同一个对象
ChildHelper mChildHelper;
RecyclerView mRecyclerView;
public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec);
public void onLayoutChildren(Recycler recycler, State state);
}
4.ViewHolder:
public abstract static class ViewHolder {
public final View itemView;
WeakReference<RecyclerView> mNestedRecyclerView;
//ViewHolder有三个标记位,它们可以用来从缓存中寻找ViewHolder缓存
int mPosition = NO_POSITION;
long mItemId = NO_ID;
int mItemViewType = INVALID_TYPE;
//表示这个ViewHolder已经被绑定到了某个position,这时mPosition,mItemId和mItemViewType都是正确有效的
static final int FLAG_BOUND = 1 << 0;
//表示这个ViewHolder对应的数据已经过期,需要重新被adapter绑定,这时mPosition和mItemId是相匹配的
static final int FLAG_UPDATE = 1 << 1;
//ViewHolder失效意味着mPosition和ItemId已经不可信,它们不再与mItemViewType匹配,此时需要重新绑定
static final int FLAG_INVALID = 1 << 2;
//表示当前ViewHolder指向的数据已经从数据集中被移除,但这个ViewHolder仍然可能正在被使用
static final int FLAG_REMOVED = 1 << 3;
//表示这个ViewHolder是从scrap缓存中返回的
static final int FLAG_RETURNED_FROM_SCRAP = 1 << 5;
//当View被detached的时候
static final int FLAG_TMP_DETACHED = 1 << 8;
}
5.ChildHelper
class ChildHelper {
final Callback mCallback;
//这个列表用来存储从屏幕上消失(称为remove)的View
final List<View> mHiddenViews;
interface Callback {
...
void attachViewToParent(View child, int index, ViewGroup.LayoutParams layoutParams);
void detachViewFromParent(int offset);
void onEnteredHiddenState(View child);
void onLeftHiddenState(View child);
}
}
6.RecycledViewPool
public static class RecycledViewPool {
private static final int DEFAULT_MAX_SCRAP = 5; //默认每个type的viewHolder最多存5个
//每个ScrapData里有一个ViewHolder列表,这些列表中存放的ViewHolder都有同样viewType
static class ScrapData {
final ArrayList<ViewHolder> mScrapHeap = new ArrayList<>();
int mMaxScrap = DEFAULT_MAX_SCRAP;
long mCreateRunningAverageNs = 0;
long mBindRunningAverageNs = 0;
}
//这里相当于用一个二位数组存储ViewHolder,第一个维度就是viewType
SparseArray<ScrapData> mScrap = new SparseArray<>();
//从RecycledViewPool取ViewHolder只能依据viewType,而不能依据position,所以这个缓存优先级比较低
public ViewHolder getRecycledView(int viewType) {
final ScrapData scrapData = mScrap.get(viewType);
if (scrapData != null && !scrapData.mScrapHeap.isEmpty()) {
final ArrayList<ViewHolder> scrapHeap = scrapData.mScrapHeap;
return scrapHeap.remove(scrapHeap.size() - 1);
}
return null;
}
}
2.缓存机制:
1.自定义Adapter的时候要实现两个重要的方法:
public class RecyclerAdapter extends RecyclerView.Adapter {
//这个方法用来创建一个viewHolder,当所有缓存都没有合适的viewHolder时,就会调用这个方法创建viewHolder;
public ItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
//最后这个view会被从viewHolder中取出来通过addView添加到RecyclerView
//所以viewHolder和view是一对一的关系
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
ItemViewHolder holder = new ItemViewHolder(view);
return holder;
}
//viewHolder和数据不是一对一的关系,一个View可能会显示多种数据,这个方法就是用来更换viewHolder中绑定的数据的
//如果获取的ViewHolder是从RecycledViewPool得到,或是由onCreateViewHolder创建,该方法都会执行
public void onBindViewHolder(ItemViewHolder holder, int position) {
String text = dataList.get(position).getName();
holder.textView.setText(text);
}
}
缓存机制的最主要的目的,就是让RecyclerView内部尽可能少的调用onCreateViewHolder和onBindViewHolder这两个方法
2.onCreateViewHolder和onBindViewHolder方法在哪里使用:
ViewHolder tryGetViewHolderForPositionByDeadline(int position,boolean dryRun, long deadlineNs) {
ViewHolder holder = null;
// 1.如果在预布局阶段,先试图从mChangedScrap中获取一个缓存
if (mState.isPreLayout()) {
holder = getChangedScrapViewForPosition(position);
}
// 2.这个方法会分别尝试从mAttachedScrap,mChildHelper,mCachedViews中获取ViewHolder
// 注意这个等级的缓存只需要依据position获取,获取到的type一定是正确的
if (holder == null) {
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
}
if (holder == null) {
// 3.如果mAdapter被设置为有固定的id,则会遍历mAttachedScrap和mCachedViews找到id匹配的viewHolder返回
if (mAdapter.hasStableIds()) {
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);
}
// 4.试图从用户自定义的缓存中获取缓存,默认获取不到
if (holder == null && mViewCacheExtension != null) {
final View view = mViewCacheExtension.getViewForPositionAndType(this, position, type);
holder = getChildViewHolder(view);
}
}
//5.试图从RecycledViewPool中根据type获取一个恢复出厂设置的缓存对象
//注意从RecycledViewPool中只能依据type获取了,因为position信息已经被丢弃了
if (holder == null) {
holder = getRecycledViewPool().getRecycledView(type);
}
//6.获取不到任何缓存,只能利用Adapter重新创建一个viewHolder
//这个createViewHolder方法就用到我们在自定义Adapter时必须重写的方法
if (holder == null) {
holder = mAdapter.createViewHolder(RecyclerView.this, type); //1
}
//7.现在ViewHolder创建好了,该给这个ViewHolder绑定数据和位置信息了
boolean bound = false;
//如果从RecycledViewPool获取,或新创建了ViewHolder,就会符合条件进入这个分支
if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
//这个方法间接地调用了onBindViewHolder这个方法,也就是我们在Adapter重写的方法
bound = tryBindViewHolderByDeadline(holder, offsetPosition, position, deadlineNs); //2
}
return holder;
}
注释1和注释2处就是我们在Adapter中重写的两个重要方法使用的位置
3.获取缓存的方式:
1.通过position获取:
为什么可以用position去获取一个View?
试想如果一个刚划出屏幕的一个View的位置信息还没有丢弃,现在又要滑进屏幕,那么可以根据这个position的值从缓存直接拿到这个View,这个View无需做任何修改,所以用position获取View的效率是最高的,例如:
//从mChangedScrap中获取ViewHolder缓存
holder = getChangedScrapViewForPosition(position);
//从mAttachedScrap,mChildHelper,mCachedViews中获取ViewHolder缓存
holder = getScrapOrHiddenOrCachedHolderForPosition(position, dryRun);
从这些缓存中取ViewHolder的方式就是遍历并对比position,例如:
//从mAttachedScrap取缓存
final int scrapCount = mAttachedScrap.size();
for (int i = 0; i < scrapCount; i++) {
final ViewHolder holder = mAttachedScrap.get(i);
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
//从mCachedViews取缓存
final int cacheSize = mCachedViews.size();
for (int i = 0; i < cacheSize; i++) {
final ViewHolder holder = mCachedViews.get(i);
return holder;
}
2.通过id获取:
if (mAdapter.hasStableIds()) {
//这里是从mAttachedScrap和mCachedViews中根据id和type获取ViewHolder
holder = getScrapOrCachedViewForId(mAdapter.getItemId(offsetPosition),type, dryRun);
}
先来看一下这个ID是从哪来的:
public abstract static class Adapter<VH extends ViewHolder> {
private boolean mHasStableIds = false; //默认没有固定的ID
public void setHasStableIds(boolean hasStableIds) {
mHasStableIds = hasStableIds;
}
public final void bindViewHolder(@NonNull VH holder, int position) {
//在ViewHolder绑定之前会给这个ViewHolder赋上一个与position相关的值
if (hasStableIds()) {
holder.mItemId = getItemId(position);
}
onBindViewHolder(holder, position, holder.getUnmodifiedPayloads());
}
//想要设置ViewHolder的ID需要重写这个方法
public long getItemId(int position) {
//根据positon计算得到一个id,可以直接把position作为id
return position;
}
}
public abstract static class ViewHolder {
long mItemId = NO_ID; //Adapter不支持固定ID,ViewHolder的默认id就是NO_ID
public final long getItemId() {
return mItemId;
}
}
通过id获取ViewHolder的方式同样是遍历并对比id,例如:
ViewHolder getScrapOrCachedViewForId(long id, int type, boolean dryRun) {
//从mAttachedScrap中获取ViewHolder缓存
final int count = mAttachedScrap.size();
for (int i = count - 1; i >= 0; i--) {
final ViewHolder holder = mAttachedScrap.get(i);
if (holder.getItemId() == id && type == holder.getItemViewType()){
holder.addFlags(ViewHolder.FLAG_RETURNED_FROM_SCRAP);
return holder;
}
}
//从mCachedViews中获取ViewHolder缓存
final int cacheSize = mCachedViews.size();
for (int i = cacheSize - 1; i >= 0; i--) {
final ViewHolder holder = mCachedViews.get(i);
if (holder.getItemId() == id && type == holder.getItemViewType()) {
return holder;
}
}
}
有固定Id的好处是,这些View会优先考虑放在效率较高的缓存中,通过id寻找ViewHolder时也会尽可能从高效的缓存中获取,adapter设置固定id可以提高缓存效率
3.通过viewType获取:
//从RecycledViewPool中获取
holder = getRecycledViewPool().getRecycledView(type);
//创建ViewHolder也需要viewType
holder = mAdapter.createViewHolder(RecyclerView.this, type);
在RecycledViewPool中会把每种不同type的viewHolder分别放在不同列表中,参考前面RecycledViewPool的结构
4.数据更新:
1.notifyDataSetChanged
public abstract static class Adapter<VH extends ViewHolder> {
private final AdapterDataObservable mObservable = new AdapterDataObservable();
public final void notifyDataSetChanged() {
mObservable.notifyChanged();
}
}
这里的mObservable是一个RecyclerViewDataObserver对象
private class RecyclerViewDataObserver extends AdapterDataObserver {
public void onChanged() {
processDataSetCompletelyChanged(true); //执行数据彻底修改
if (!mAdapterHelper.hasPendingUpdates()) {
//这里调用了View的requestLayout,这个方法会触发Measure,Layout,draw过程;
requestLayout();
}
}
}
看下修改数据做了什么:
//把mChildHelper中所有的ViewHolder加上无效标签
final int childCount = mChildHelper.getUnfilteredChildCount();
for (int i = 0; i < childCount; i++) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
if (holder != null && !holder.shouldIgnore()) {
holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
}
}
//把mCachedViews中所有的ViewHolder加上无效标签
final int cachedCount = mCachedViews.size();
for (int i = 0; i < cachedCount; i++) {
final ViewHolder holder = mCachedViews.get(i);
if (holder != null) {
holder.addFlags(ViewHolder.FLAG_UPDATE | ViewHolder.FLAG_INVALID);
holder.addChangePayload(null);
}
}
//把mCachedViews中所有的ViewHolder转移到RecycledViewPool,并清除mCachedViews
final int count = mCachedViews.size();
for (int i = count - 1; i >= 0; i--) {
ViewHolder viewHolder = mCachedViews.get(i);
addViewHolderToRecycledViewPool(viewHolder, true);
}
mCachedViews.clear();
可以看到notifyDataSetChanged方法会使所有View失效,并把它们都转移到RecycledViewPool这个效率较低的缓存中,而RecycledViewPool容量有限,可能会导致ViewHolder重新创建,之后每个View都需要重新执行onBindViewHolder,所以notifyDataSetChanged效率较低;
2.notifyItemChanged
public final void notifyItemChanged(int position) {
mObservable.notifyItemRangeChanged(position, 1);
}
onItemRangeChanged:
private class RecyclerViewDataObserver extends AdapterDataObserver {
public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
//mAdapterHelper是一个专门用来处理adapter数据更新的工具类
if (mAdapterHelper.onItemRangeChanged(positionStart, itemCount, payload)) {
triggerUpdateProcessor(); //执行了requestLayout();
}
}
}
5.ViewHolder加入缓存的过程:
当view移出屏幕时,会间接触发如下方法:
//className = LayoutManager
public void removeAndRecycleViewAt(int index, Recycler recycler) {
final View view = getChildAt(index);
recycler.recycleView(view);
}
然后RecyclerView方法又间接调用者如下方法:
void recycleViewHolderInternal(ViewHolder holder) {
if (forceRecycle || holder.isRecyclable()) {
if (mViewCacheMax > 0
&& !holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID //invalidViewHolder不放进mCachedViews
| ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE
| ViewHolder.FLAG_ADAPTER_POSITION_UNKNOWN)) {
int cachedViewSize = mCachedViews.size();
if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
//这里发现mCachedViews已经装满了
//把最老的viewHolder转移到RecycledViewPool
//进入RecycledViewPool的viewHolder的position等信息被丢弃
//所以从这个缓存拿到的viewHoler需要重新执行onBindViewHolder绑定数据
recycleCachedViewAt(0);
cachedViewSize--;
}
//如果mCachedViews没满,把移出屏幕的viewHolder放到mCachedViews中去
//在mCachedViews中的viewHolder保留了position等信息,无需重新绑定数据
mCachedViews.add(targetCacheIndex, holder);
cached = true;
}
if (!cached) {
addViewHolderToRecycledViewPool(holder, true);
recycled = true;
}
}
}
把一个缓存添加到RecycledViewPool:
void addViewHolderToRecycledViewPool(ViewHolder holder, boolean dispatchRecycled) {
//...
getRecycledViewPool().putRecycledView(holder);
}
public void putRecycledView(ViewHolder scrap) {
final int viewType = scrap.getItemViewType();
final ArrayList<ViewHolder> scrapHeap = getScrapDataForType(viewType).mScrapHeap;
if (mScrap.get(viewType).mMaxScrap <= scrapHeap.size()) {
return;
}
scrap.resetInternal();
scrapHeap.add(scrap);
}
6.scrap的作用:
从ViewHolder的回收过程可以看到,移出屏幕的viewHolder都被直接放进了mCachedViews中,并没有mAttachedScrap和mChangedScrap参与,下面通过recyclerView的重布局研究Scrap缓存的作用。
从上文可以看出,notifyDataSetChanged和notifyItemChanged方法都执行了requestLayout方法,这个方法可以触发View重新Measure,Layout,Draw过程,重布局从RecyclerView的onMeasure看起:
onMeasure
//这里的mLauout就是LayoutManager
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
return;
}
if (mLayout.isAutoMeasureEnabled()) {
...
mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
if (mState.mLayoutStep == State.STEP_START) {
dispatchLayoutStep1(); //Step1是预布局阶段
}
dispatchLayoutStep2(); //Step2是正式布局阶段
}
}
private void dispatchLayoutStep2() {
...
mLayout.onLayoutChildren(mRecycler, mState); //LayoutManager去布局子控件
}
mLayout是个抽象类,这里以LinerLayoutManager为例:
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
detachAndScrapAttachedViews(recycler); //先把Recycler中的ViewHolder全部detach并放进缓存
...
fill(recycler, mLayoutState, state, false); //填充recyclerView
}
移除ViewHolder的逻辑:
//className = LinerLayoutManager
public void detachAndScrapAttachedViews(Recycler recycler) {
final int childCount = getChildCount();
//遍历回收RecyclerView中的每个View
for (int i = childCount - 1; i >= 0; i--) {
final View v = getChildAt(i);
scrapOrRecycleView(recycler, i, v);
}
}
private void scrapOrRecycleView(Recycler recycler, int index, View view) {
final ViewHolder viewHolder = getChildViewHolderInt(view);
if (viewHolder.isInvalid() && !viewHolder.isRemoved() && !mRecyclerView.mAdapter.hasStableIds()) {
//如果View已经失效了,就放入RecycledViewPool中(参考上文回收View的过程)
removeViewAt(index);
recycler.recycleViewHolderInternal(viewHolder);
} else {
//如果这个View还能继续用,就简单暂存在mAttachedScrap
//后面就可以无需重新绑定直接从mAttachedScrap取出并填充RecyclerView
detachViewAt(index);
recycler.scrapView(view);
mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
//className = Recycler
void scrapView(View view) {
final ViewHolder holder = getChildViewHolderInt(view);
...
mAttachedScrap.add(holder); //把从RecyclerView上detach下来的ViewHolder放进缓存
}
填充RecyclerView:
//className = LinerLayoutManager
int fill(RecyclerView.Recycler recycler, LayoutState layoutState,
RecyclerView.State state, boolean stopOnFocusable) {
...
layoutChunk(recycler, state, layoutState, layoutChunkResult);
...
}
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
...
View view = layoutState.next(recycler); //这里会从缓存中取一个View出来
addView(view);
}
//className = LayoutState
View next(RecyclerView.Recycler recycler) {
if (mScrapList != null) {
return nextViewFromScrapList(); //优先从mAttachedScrap取缓存
}
//这个方法会调用上文介绍的tryGetViewHolderForPositionByDeadline方法
final View view = recycler.getViewForPosition(mCurrentPosition);
return view;
}
总结:
缓存主要包括Scrap,CachedView,RecyclerPool;
Scrap中的缓存主要用于Recycler重新布局时用于临时存放屏幕上所有的ViewHolder;
CachedView中ViewHolder缓存的特点是里面的position信息没有失效,从CachedView中获取的缓存无需重新绑定数据就可以直接显示在屏幕上;
RecyclerPool的是一个可以存放多种ViewType的缓存,每个ViewType对应一个数组,这些数组又组成RecyclerPool,这个缓存中ViewHolder的所有信息都已被擦除,需要重新绑定数据才能显示在屏幕上,所以效率较低;
如果上面的缓存都获取不到ViewHolder,也没有实现自定义缓存的话,就需要通过Adapter的onCreateViewHolder来创建ViewHolder,然后在执行onBindViewHolder后绘制对应的View
获取缓存的方式可以通过position,id,type三者效率依次降低
notifyDataSetChanged方法会触发重布局,ViewHolder会全部擦出数据放进RecycledViewPool中,导致所有数据需要重新绑定,所以效率较低