【Android 源码】RecylerView的深入理解

文章目录

深入理解 RecyclerView 的绘制流程和滑动原理(匠心巨作-上)
深入理解 RecyclerView 的嵌套滑动机制(匠心巨作-中)
深入理解 RecyclerView 的回收复用缓存机制详解(匠心巨作-下)

绘制流程:RecyclerView的绘制三个步骤

1. setLayoutManager

必要的步骤,给RecyclerView设置布局管理器,常见的布局管理器有:线性布局管理器LinearLayoutManager,网格布局管理器GridLayoutManager,瀑布流式管理器StaggeredGridLayoutManager。
源码解析:重置回收 -> LayoutManager与RecyclerView关联起 -> 请求重绘requestLayout, 进入measure,layout,draw流程

java 复制代码
public void setLayoutManager(@Nullable LayoutManager layout) {
    // 如果新设置的 LayoutManager 和当前已有的 LayoutManager 是同一个,直接返回,无需操作
    if (layout == mLayout) {
        return;
    }

    // 停止当前的滚动操作(比如快速滑动或 fling 操作)
    stopScroll();

    // 如果当前已经有 LayoutManager,则需要先清理它相关的视图和资源
    if (mLayout != null) {
        // 如果有 ItemAnimator,结束所有正在执行的动画
        if (mItemAnimator != null) {
            mItemAnimator.endAnimations();
        }

        // 移除并回收所有由 LayoutManager管理的视图(包括已废弃的视图)
        mLayout.removeAndRecycleAllViews(mRecycler);
        mLayout.removeAndRecycleScrapInt(mRecycler); // 回收 scrap 中的视图

        // 清空 Recycler 中的所有缓存视图
        mRecycler.clear();

        // 如果 RecyclerView 已经附加到 Window 上,调用 LayoutManager 的 dispatchDetachedFromWindow
        if (mIsAttached) {
            mLayout.dispatchDetachedFromWindow(this, mRecycler);
        }

        // 将 LayoutManager 与 RecyclerView 解除绑定
        mLayout.setRecyclerView(null);

        // 置空当前 LayoutManager
        mLayout = null;
    } else {
        // 如果当前没有 LayoutManager,也清空 Recycler 的缓存视图
        mRecycler.clear();
    }

    // 为了防止 ItemAnimator 出现问题,再次移除所有子视图(防御性操作)
    mChildHelper.removeAllViewsUnfiltered();

    // 将新的 LayoutManager 赋值给成员变量
    mLayout = layout;

    // 如果新 LayoutManager 不为 null
    if (layout != null) {
        // 检查该 LayoutManager 是否已经绑定到另一个 RecyclerView,如果是,抛出异常
        if (layout.mRecyclerView != null) {
            throw new IllegalArgumentException("LayoutManager " + layout
                    + " is already attached to a RecyclerView:"
                    + layout.mRecyclerView.exceptionLabel());
        }

        // 将该 LayoutManager 绑定到当前 RecyclerView
        mLayout.setRecyclerView(this);

        // 如果 RecyclerView 已经附加到 Window 上,通知 LayoutManager 附加事件
        if (mIsAttached) {
            mLayout.dispatchAttachedToWindow(this);
        }
    }

    // 更新 Recycler 的视图缓存大小
    mRecycler.updateViewCacheSize();

    // 请求重新布局,触发 LayoutManager 重新布局子视图
    requestLayout();
}

2. dispatchLayoutStep系列方法

理解measure,layout,draw流程之前需要先理解dispatchLayoutStep系列方法,这一系列方法分别对应 RecyclerView 在执行布局时的三个主要阶段。此处省略源码,只需要理解其核心作用。

  • dispatchLayoutStep1()
    预处理 :表示进行预布局,适配器更新、动画运行、保存当前视图的信息等工作;
    mState.mLayoutStep == State.STEP_START才会执行。
    onMeasure,onLayout方法都可能会执行
  • dispatchLayoutStep2()
    核心阶段 ,测量和布局所有需要显示的 itemView。
    onMeasure,onLayout方法都可能会执行
  • dispatchLayoutStep3()
    后处理 :表示布局最后一步,保存和触发有关动画的信息,相关清理等工作。
    onLayout中执行

3. onMeasure

  • 源码理解
    onMeasure()主要是RecyclerView宽高测量工作,主要有两种情况:
    (1)当RecyclerView的宽高为match_parent或者精确值时 ,即measureSpecModeIsExactly = true,此时只需要测量自身的宽高 就知道RecyclerView的宽高,测量方法结束(defaultOnMeasure);
    (2)当RecyclerView的宽高为wrap_content时 ,即measureSpecModeIsExactly = false,会往下执行dispatchLayoutStep1()和dispatchLayoutStep2(),就是遍历测量ItemView的大小从而确定RecyclerView的宽高,这种情况真正的测量操作都是在dispatchLayoutStep2()中完成。
java 复制代码
    @Override
    protected void onMeasure(int widthSpec, int heightSpec) {
        if (mLayout == null) {//如果mLayout为空则采用默认测量,然后结束
            defaultOnMeasure(widthSpec, heightSpec);
            return;
        }
        if (mLayout.mAutoMeasure) {//如果为自动测量,默认为true
        	final int widthMode = MeasureSpec.getMode(widthSpec);
            final int heightMode = MeasureSpec.getMode(heightSpec);
        	mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);//测量RecyclerView的宽高
        	 //当前RecyclerView的宽高是否为精确值
        	final boolean measureSpecModeIsExactly =
                    widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY;
            if (measureSpecModeIsExactly || mAdapter == null) {//如果RecyclerView的宽高为精确值或者mAdapter为空,则结束
                return;
            }
            //RecyclerView的宽高为wrap_content时,即measureSpecModeIsExactly = false则进行测量
            //因为RecyclerView的宽高为wrap_content时,需要先测量itemView的宽高才能知道RecyclerView的宽高
            if (mState.mLayoutStep == State.STEP_START) {//还没测量过
                dispatchLayoutStep1();//1.适配器更新、动画运行、保存当前视图的信息、运行预测布局
            }
            dispatchLayoutStep2();//2.最终实际的布局视图,如果有必要会多次运行
            //根据itemView得到RecyclerView的宽高
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
        }
    }

4. onLayout

  • 源码理解
    1、如果没在onMeasure阶段提前测量子ItemView ,即RecyclerView宽高为match_parent或者精确值时,调用dispatchLayoutStep1()和dispatchLayoutStep2()测量itemView宽高
    2、如果在onMeasure阶段提前测量子ItemView ,但是子视图发生了改变或者期望宽高和实际宽高不一致,则会调用dispatchLayoutStep2()重新测量
    3、最后都会执行dispatchLayoutStep3()方法
java 复制代码
   @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
        dispatchLayout(); //直接调用dispatchLayout()方法布局
        TraceCompat.endSection();
        mFirstLayoutComplete = true;
    }
    
  void dispatchLayout() {
  		······
        mState.mIsMeasuring = false;//设置RecyclerView布局完成状态,前面已经设置预布局完成了。
        if (mState.mLayoutStep == State.STEP_START) {//如果没在OnMeasure阶段提前测量子ItemView
            dispatchLayoutStep1();//布局第一步:适配器更新、动画运行、保存当前视图的信息、运行预测布局
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();
        } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth()
                || mLayout.getHeight() != getHeight()) {//前两步完成测量,但是因为大小改变不得不再次运行下面的代码
            mLayout.setExactMeasureSpecsFrom(this);
            dispatchLayoutStep2();//布局第二步:最终实际的布局视图,如果有必要会多次运行
        } else {
            mLayout.setExactMeasureSpecsFrom(this);
        }
        dispatchLayoutStep3();//布局第三步:最后一步的布局,保存视图动画、触发动画和不必要的清理。
    }

5. onDraw

  • 源码解析
    在 RecyclerView 自身绘制完成后,依次调用所有添加的 ItemDecoration 的 onDraw() 方法,用于在 itemView 绘制之前绘制底层装饰内容。
    ItemDecoration 是 RecyclerView 提供的一个抽象类,用于在 item 绘制前后添加装饰效果,比如:设置 item 的间隔,添加分组标题绘制边框、阴影等视觉效果
java 复制代码
    @Override
    public void onDraw(Canvas c) {
        super.onDraw(c);//所有itemView先绘制
		//分别绘制ItemDecoration
        final int count = mItemDecorations.size();
        for (int i = 0; i < count; i++) {
            mItemDecorations.get(i).onDraw(c, this, mState); 
        }
    }

6. 绘制流程总结

绘制流程:LinearLayoutManager填充、测量、布局过程

RecyclerView 的回收复用缓存机制

相关推荐
电饭叔2 小时前
一个虚假证明的错误(一)
学习
weixin_403810132 小时前
EasyClick 安卓自动化版本 如何自激活代理模式并且启动安卓的自动化服务
android·自动化·代理模式
爱吃大芒果2 小时前
Flutter for OpenHarmony核心组件学习: MaterialApp、Scaffold 两大基础组件以及有无状态组件
开发语言·学习·flutter
xiaobuding_QAQ2 小时前
51汇编仿真proteus8.15学习篇三(附源码)
汇编·单片机·学习·proteus
rannn_1112 小时前
【Javaweb学习|Day11】SpringBoot原理|配置优先级、Bean的管理、原理及源码分析
java·spring boot·后端·学习·javaweb
微软Dynamics 365培训2 小时前
长沙爱码士IT学院Dynamics 365 &Power Platform部分学习模块拆解
学习
马猴烧酒.2 小时前
智能协图云图库学习笔记day5
java·jvm·spring boot·笔记·学习·mvc
2501_933513042 小时前
Java后端开发者的AGI时代学习与职业路径策略
java·学习·agi
救救孩子把2 小时前
60-机器学习与大模型开发数学教程-5-7 学习率调度(warmup、余弦退火、OneCycle)
人工智能·学习·机器学习