Android RecyclerView 原理解析

本文详细剖析了Android RecyclerView的内部原理,涵盖模块分析,包括列表控件、LayoutManager和Recycler回收机制。重点讲解了View布局流程,如LinearLayoutManager的onLayoutChildren方法、fill方法、recycleByLayoutState和layoutChunk。同时,阐述了滚动时view的展示与复用,以及数据刷新的局部刷新策略。最后讨论了获取ViewHolder的position、position和itemId的区别以及Divider的绘制方法。
摘要由CSDN通过智能技术生成


android最原始的复用型列表View是ListView,具体原理可以参照 这篇文章

一.模块分析

1.列表控件

继承关系
在这里插入图片描述

2.LayoutManager

RecyclerView支持列表、表格、瀑布流三种布局样式,与ListView自身决定布局样式不同的是,RecyclerView使用其静态内部类LayoutManager来管理具体的布局样式,其子类可以实现自己的对于子view的测量,布局等功能

RecyclerView提供了三种LayoutManager,LinearLayoutManager、GridLayoutManager和StaggeredGridLayoutManager,用户还可以通过继承LayoutManager来实现自己的布局管理器

3.Recycler回收机制

这货就是回收view的主要类,他是RecyclerView的一个内部类,管理着对view的分类分级回收,与ListView的RecycleBin类功能类似

class Recycler{
   
 
//主要field
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();//回收attached的view的,与ListView的activeViews类似
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();//一级缓存,可设置大小,默认缓存大小为2
private RecycledViewPool mRecyclerPool;//缓存池对象,多个RecyclerView可以指定使用一个pool对象
private ViewCacheExtension mViewCacheExtension;//自定义的缓存机制对象,用户可以继承之实现自己的一个缓存对象,在指定时机会使用该对象里的缓存对象
	
//主要方法
//1.获取缓存view
View getViewForPosition(int position, boolean dryRun) {
   
    ...
    // 1) Find from scrap by position
    if (holder == null) {
   
        holder = getScrapViewForPosition(position, INVALID_TYPE, dryRun);//从scrapView里(attachViews或者一级缓存cachedViews)根据position获取view
        if (holder != null) {
   
            if (!validateViewHolderForOffsetPosition(holder)) {
   
                // recycle this scrap
                ...
            } else {
   
                fromScrap = true;
            }
        }
    }
    if (holder == null) {
   
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        ...
        final int type = mAdapter.getItemViewType(offsetPosition);
        // 2) Find from scrap via stable ids, if exists
        if (mAdapter.hasStableIds()) {
   //有stableId就从scrapView里(attachViews或者一级缓存cachedViews)根据itemId获取view
            holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
            if (holder != null) {
   
                // update position
                holder.mPosition = offsetPosition;
                fromScrap = true;
            }
        }
        if (holder == null && mViewCacheExtension != null) {
   //此时如果有自定义的cache会尝试从中复用
            // We are NOT sending the offsetPosition because LayoutManager does not
            // know it.
            final View view = mViewCacheExtension
                    .getViewForPositionAndType(this, position, type);
            ...
        }
        if (holder == null) {
    // fallback to recycler
            ...
            holder = getRecycledViewPool().getRecycledView(type);//从pool里获取
            ...
        }
        if (holder == null) {
   
            holder = mAdapter.createViewHolder(RecyclerView.this, type);//新建ViewHolder
            ...
        }
    }
	...
    boolean bound = false;
    if (mState.isPreLayout() && holder.isBound()) {
   
        // do not update unless we absolutely have to.
        holder.mPreLayoutPosition = position;
    } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
   
        ...
        final int offsetPosition = mAdapterHelper.findPositionOffset(position);
        holder.mOwnerRecyclerView = RecyclerView.this;
        mAdapter.bindViewHolder(holder, offsetPosition);//需要绑定数据
        attachAccessibilityDelegate(holder.itemView);
        bound = true;
        if (mState.isPreLayout()) {
   
            holder.mPreLayoutPosition = position;
        }
    }
	//设置lp
    final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
    final LayoutParams rvLayoutParams;
    if (lp == null) {
   
        rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
        holder.itemView.setLayoutParams(rvLayoutParams);
    } else if (!checkLayoutParams(lp)) {
   
        rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
        holder.itemView.setLayoutParams(rvLayoutParams);
    } else {
   
        rvLayoutParams = (LayoutParams) lp;
    }
    rvLayoutParams.mViewHolder = holder;
    rvLayoutParams.mPendingInvalidate = fromScrap && bound;
    return holder.itemView;
}
 
//2.从pool里获取指定type的缓存对象
public ViewHolder getRecycledView(int viewType) {
   
    final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
    if (scrapHeap != null && !scrapHeap.isEmpty()) {
   
        final int index = scrapHeap.size() - 1;
        final ViewHolder scrap = scrapHeap.get(index);
        scrapHeap.remove(index);
        return scrap;
    }
    return null;
}
 
//3.回收ViewHolder
void recycleViewHolderInternal(ViewHolder holder) {
   
    ...
    if (forceRecycle || holder.isRecyclable()) {
   
        if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED
                | ViewHolder.FLAG_UPDATE)) {
   
            // Retire oldest cached view
            final int cachedViewSize = mCachedViews.size();
            if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) {
   //一级缓存满了的话移除一个到pool里,然后再放入到一级缓存里
                recycleCachedViewAt(0);
            }
            if (cachedViewSize < mViewCacheMax) {
   
                mCachedViews.add(holder);
                cached = true;
            }
        }
        if (!cached) {
   //如果没有加入到一级缓存中,则放入pool里缓存
            addViewHolderToRecycledViewPool(holder);
            recycled = true;
        }
    }...
}
 
//4.根据type放入到指定type的缓存集合中
public void putRecycledView(ViewHolder scrap) {
   
    final int viewType = scrap.getItemViewType();
    final ArrayList scrapHeap = getScrapHeapForType(viewType);
    ...
    scrap.resetInternal();
    scrapHeap.add(scrap);
}
 
//5.缓存attach的view
void scrapView(View view) {
   
    final ViewHolder holder = getChildViewHolderInt(view);
    if (holder.hasAnyOfTheFlags(ViewHolder.FLAG_REMOVED | ViewHolder.FLAG_INVALID)
            || !holder.isUpdated() || canReuseUpdatedViewHolder(holder)) {
   //满足条件的attach的view放入attached集合中
        ...
        holder.setScrapContainer(this, false);
        mAttachedScrap.add(holder);
    }...
}
}

可以通过RecyclerView的setRecycledViewPool来设置该view的pool对象,exa,一个ViewPager的多个RecyclerView展示使用相同种类的view和type,就可以公用一个pool来缓存对象,提升缓存效率

可以通过RecyclerView的setItemViewCacheSize来设置一级缓存对象个数,默认为2

可以通过RecyclerView的setViewCacheExtension来设置自定义的扩展缓存,用户实现时重写getViewForPositionAndType方法,根据position和type来决定复用哪个对象(该cache并不会去缓存对象),在从pool里取之前调用该方法进行复用

二.View布局流程分析

在这里插入图片描述
这里说明一下23.2.0后RecyclerView支持height为WRAP_CONTENT情况(之前版本都是和MATCH_PARENT一样处理),该情况会在onMeausre时就调用dispatchLayoutStep2进行布局,尽量不要使用

布局子view主要是通过不同的LayoutManager实现的onLayoutChildren方法实现,大体实现思路就是计算当前绘制点的信息,包括坐标点和item的position等,并循环在剩余空间(最多一屏)里获取view并填充,该view的获取由多级缓存实现,包括attach的集合、一级缓存的集合、自定义的缓存以及可公用缓存池

与ListView的detach+attach填充view不同的是,RecyclerView在回收view时remove掉,再次复用时add进来,理论上会触发多次requestLayout,但是RecyclerView重写了该方法,在里面有拦截逻辑,使得不会触发多次,所以不用担心效率问题

dispatchLayoutStep1和3主要处理notifyItem及其动画事件,后续会单独分析,这里只说实际布局子view的dispatchLayoutStep2

1.LinearLayoutManager的onLayoutChildren()方法

public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
   
    // layout algorithm:
    // 1) by checking children and other variables, find an anchor coordinate and an anchor
    //  item position.
    // 2) fill towards start, stacking from bottom
    // 3) fill towards end, stacking from top
    // 4) scroll to fulfill requirements like stack from bottom.
    ...
    updateAnchorInfoForLayout(recycler, state, mAnchorInfo);//更新布局锚点信息
    ...
    detachAndScrapAttachedViews(recycler
  • 0
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值