前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >recyclerView源码解析

recyclerView源码解析

原创
作者头像
用户10208357
发布2022-11-20 22:58:03
7340
发布2022-11-20 22:58:03
举报
文章被收录于专栏:用户10208357的专栏

1、recyclerView的基本使用

使用recyclerView主要需要了解RecyclerViewAdapter、layoutManager。重写recyclerViewAdapter的三个方法,onCreateViewHodler、onBindViewHolder、getItemCount。Adpater的职责是对用数据对每个item进行填充。layoutManager的作用主要就是用摆放item的位置。

2、recyclerkView源码解析

首先看recyclerView的注释

adapter:正如前文所诉,RecyclerView.Adapter 负责提供表示数据集中项目的视图。

index:子view的索引,可以用getChidAt()根据索引获得子view。

Recycle:缓存之前显示过的view。

Scrap(view):在布局时进入暂时分离状态的子视图。可以在不完全脱离父 RecyclerView 的情况下重用 Scrap 视图,如果不需要重新绑定则不修改,或者如果视图被认为是脏的则由适配器修改。

Dirty (view): 脏view,dirtyView显示在界面上上必须重新绑定。

2.1recyclerView的测量方法

从源码可以看出,在mLayout就是LayoutManger为null时,recyclerView会走defaultOnMeasure(),mLayout的自动测量模式就是,返回是否应该使用 RecyclerView 的 AutoMeasure 机制,或者是否应该由 LayoutManager 的 onMeasure(RecyclerView.Recycler, RecyclerView.State, int, int) 实现来完成。

代码语言:javascript
复制
protected void onMeasure(int widthSpec, int heightSpec) {
    if (mLayout == null) {
        //View.onMeasure(int, int) 的实现,可在此 RecyclerView 缺乏更好信息的各种场景中回退。
        defaultOnMeasure(widthSpec, heightSpec);
        return;
    }
    if (mLayout.isAutoMeasureEnabled()) {
        //如果开启了自动测量模式
        final int widthMode = MeasureSpec.getMode(widthSpec);
        final int heightMode = MeasureSpec.getMode(heightSpec);
        
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);

        // Calculate and track whether we should skip measurement here because the MeasureSpec
        // modes in both dimensions are EXACTLY.
        //是否应该跳过自动测量,如果MeasureSpec是精确的
        mLastAutoMeasureSkippedDueToExact =
                    widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
        if (mLastAutoMeasureSkippedDueToExact || 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();

RecyclerView的测量工作,是需要dispatchLayoutStep1,dispatchLayoutStep2,dispatchLayoutStep3这三步来执行的,step1里是进行预布局,主要跟记录数据更新时需要进行的动画所需的信息有关,step2就是实际循环执行了子View的测量布局的一步,而step3主要是用来实际执行动画。而且通过mLayoutStep记录了当前执行到了哪一步。在开启自动测量的情况下如果没有设置固定宽高,那么会执行setp1和step2。在step2执行完后就可以调用setMeasuredDimensionFromChildren方法,根据子类的测量布局结果来设置自身的大小

2.2recyclerView的onlayout

代码语言:javascript
复制
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
    dispatchLayout();
    TraceCompat.endSection();
    mFirstLayoutComplete = true;
}

    void dispatchLayout() {
        if (mAdapter == null) {//没有设置adapter,返回
            Log.e(TAG, "No adapter attached; skipping layout");
            // leave the state in START
            return;
        }
        if (mLayout == null) {//没有设置LayoutManager,返回
            Log.e(TAG, "No layout manager attached; skipping layout");
            // leave the state in START
            return;
        }
        mState.mIsMeasuring = false;
        //在onMeasure阶段,如果宽高是固定的,那么mLayoutStep == State.STEP_START 而且dispatchLayoutStep1和dispatchLayoutStep2不会调用
        //所以这里就会调用一下
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()|| mLayout.getHeight() != getHeight()) {
            //在onMeasure阶段,如果执行了dispatchLayoutStep1,但是没有执行dispatchLayoutStep2,就会执行dispatchLayoutStep2
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else {
            mLayout.setExactMeasureSpecsFrom(this);
        }
        //最终调用dispatchLayoutStep3
        dispatchLayoutStep3();
    }

onLayout阶段会根据onMeasure阶段3个步骤执行到了哪个,然后会在onLayout中把剩下的步骤执行。

2.3recyclerView的回收复用

Recycler是recyclerkview的内部类,主要用于视图的回收和复用。

mCachedViews 一级缓存:ViewHolder数据还在,只有原来的position可用,不需要重新绑定数据 mCachedViews 默认大小为 2

RecycledViewPool 二级缓存:ViewHolder数据重置,需要重新绑定数据

detach操作,是一种轻量级的分离,只会把这个View在ViewGroup的children数组中的引用设为null,并设置他的parent为null。当一个视图被分离时,它的父级为 null 并且不能通过调用getChildAt(int)来检索。

在进行布局时回收,子View进行detach,如果布局完成后,还在显示区域内显示,那么就会重新attach。其他不显示的就会被remove。

代码语言:javascript
复制
//主要field
final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
//一级缓存,可设置大小,默认缓存大小为2
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
//缓存池对象,多个RecyclerView可以指定使用一个pool对象
private RecycledViewPool mRecyclerPool;
//自定义的缓存机制对象,用户可以继承之实现自己的一个缓存对象,在指定时机会使用该对象里的缓存对象
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);
    }...
}
}

2.4ConcatAdapter

一个recyclerView使用两个adapter,文本列表的相关逻辑用一个Adapter中实现的,处理Button列表相关的逻辑用另一个Adapter中实现的,这样以来就降低了代码的耦合性,并提高了代码的复用性。

2.5ItemTouchHelper

itemtouchhelper是recyclerView的一个工具类,为recyclerView提供了滑动消失和拖拽删除的功能。

代码语言:javascript
复制
  //声明一个Callback
    ItemTouchHelper.Callback callback = new ItemTouchHelper.Callback() {
        @Override
        public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
            //返回方向值
            return 0;
        }

        @Override
        public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
            //拖拽处理
            return false;
        }

        @Override
        public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
            //滑动处理
        }
    };
    //创建helper对象
    ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
    //关联recyclerView
    itemTouchHelper.attachToRecyclerView(recyclerview);

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1、recyclerView的基本使用
  • 2、recyclerkView源码解析
    • 2.1recyclerView的测量方法
      • 2.2recyclerView的onlayout
        • 2.3recyclerView的回收复用
          • 在进行布局时回收,子View进行detach,如果布局完成后,还在显示区域内显示,那么就会重新attach。其他不显示的就会被remove。
          • 2.5ItemTouchHelper
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档