RecyclerView源码分析(二)--测量流程

阅读本文您大概需要4.33分钟。

相关系列文章

RecyclerView源码分析(一)--整体设计

在上一篇文章中主要讲解了RecyclerView内部的大体设计结构。因为是从结构出发,所以多少对细节讲的云里雾里。那么从这一篇开始要落实到代码细节中了。

既然RecyclerView是一个GroupView,那么也就是一个View。那么View的绘制过程是measure到layout到draw的一个顺序。然而一个GroupView的目的是盛放其它View的,那么最主要的还是其measure和layout过程。那么我们今天就来看看RecyclerView的measure过程。

PS:源码版本为24.1.1,如果下面与你的源码有出入,请核实版本是否相同。

RecyclerView的Measure过程

如果你这个时候也打开了源码,你应该会发现RecyclerView的onMeasure方法很长。那么我们先将其分情况讨论。

  1. 没有LayoutManager的情况
  2. 有LayoutManager并开启了自动测量
  3. 有LayoutManager但没有开启自动测量
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
    if (mLayout == null) {
        // 1. 没有LayoutManager的情况
        defaultOnMeasure(widthSpec, heightSpec);
        return;
    }
    if (mLayout.mAutoMeasure) {
        // 2. 有LayoutManager并开启了自动测量
        ……
    } else {
        // 3. 有LayoutManager但没有开启自动测量
        ……
    }
}

第一种情况十分简单,就是执行了defaultOnMeasure方法。里面就是计算并设置了RecyclerView的长宽值。下来我们介绍另外两种情况。

RecyclerView的自动测量过程

在RecyclerView的早期版本,当你为其设置了wrap_content值,但在其中的内容改变的时候,RecyclerView并不能改变其大小来适应内部的内容。因此后来加入了自动测量机制,来解决这个问题。而且现在我们常用的三个LayoutManager,在其构造函数中,均已经设置了开启自动测量。所以我们现在可以放心大胆的为RecyclerView设置wrap_content。

那么我们来看一下RecyclerView的自动测绘过程:

protected void onMeasure(int widthSpec, int heightSpec) {
    ……
    if (mLayout.mAutoMeasure) {
        // 第一部分:
        // 1) 首先执行LayoutManager的onMeasure方法。
        // 2) 检查如果width和height都已经是精确值,那么就不用再根据内容进行计算所
        // 需要的width和height,那么跳过之后的步骤。如果有其中任何一个值不是精确值,
        // 则进入到下面计算所需长宽的步骤。
        final int widthMode = MeasureSpec.getMode(widthSpec);
        final int heightMode = MeasureSpec.getMode(heightSpec);
        final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
                && heightMode == MeasureSpec.EXACTLY;
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        if (skipMeasure || mAdapter == null) {
            return;
        }
        // 第二部分:
        // 1) 开启布局流程计算出所有Child的边界
        // 2) 然后根据计算出的Child的边界计算出RecyclerView的所需width和height
        // 3) 检查是否需要再次测量
        if (mState.mLayoutStep == State.STEP_START) {
            dispatchLayoutStep1();
        }
        mLayout.setMeasureSpecs(widthSpec, heightSpec);
        mState.mIsMeasuring = true;
        dispatchLayoutStep2();

        // 布局过程结束,根据Children中的边界信息计算并设置RecyclerView长宽的测量值
        mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
        
        // 检查是否需要再此测量。如果RecyclerView仍然有非精确的宽和高,或者这里还有至
        // 少一个Child还有非精确的宽和高,我们就需要在此测量。
        if (mLayout.shouldMeasureTwice()) {
            mLayout.setMeasureSpecs(
                    MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
                    MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
            mState.mIsMeasuring = true;
            dispatchLayoutStep2();
            mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
        }
    } else {
        ……
    }
}

自动测绘过程可以分为两部分:

第一部分:

  1. 首先执行LayoutManager的onMeasure方法。
  2. 检查如果width和height都已经是精确值,那么就不用再根据内容进行计算所需要的width和height,那么跳过之后的步骤。如果有其中任何一个值不是精确值,则进入到下面计算所需长宽的步骤。

第二部分:

  1. 开启布局流程,计算出所有Child的边界。
  2. 然后根据计算出的Child的边界计算出RecyclerView的所需width和height,并设置。
  3. 检查是否需要再次测量,如果需要则在此进行测量。

注意:
因为自动绘制过程中执行了布局流程,那么在之后布局的时候会检查是否已经进行过布局流程,如果已经进行过布局流程,则会跳过进行过的布局流程,不会造成重复操作。(布局流程有三步)

RecyclerView的非自动测绘流程

当我们使用系统提供的那三个LayoutManager的时候,默认是开启自动测绘的。除非,你在初始化LayoutManager之后,自己通过setAutoMeasureEnabled(false)方法设置成false。不然不会走到非自动测绘流程的。但是如果我们是使用的自己自定义的LayoutManager,而且我们自定义的LayoutManager又没有在初始化的时候开启自动测绘,那么默认将会是不开启自动测绘。这个时候会走到非自动测绘流程。

那么接下来看一看非自动测绘流程。

protected void onMeasure(int widthSpec, int heightSpec) {
    ……
    if (mLayout.mAutoMeasure) {
        ……
    } else {
        // 第一部分:如果RecyclerView已经设置了Size固定,则执行LayoutManager的onMeasure方法
        if (mHasFixedSize) {
            mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
            return;
        }
        // 第二部分:
        // 1) 如果在测量的过程中有数据有更新,则先处理更新的数据
        // 2) 执行自定义测量流程,这需要自定义的LayoutManager实现onMeasure方法。
        if (mAdapterUpdateDuringMeasure) {
            eatRequestLayout();
            processAdapterUpdatesAndSetAnimationFlags();

            if (mState.mRunPredictiveAnimations) {
                mState.mInPreLayout = true;
            } else {
                // consume remaining updates to provide a consistent state with the layout pass.
                mAdapterHelper.consumeUpdatesInOnePass();
                mState.mInPreLayout = false;
            }
            mAdapterUpdateDuringMeasure = false;
            resumeRequestLayout(false);
        }
        // 处理完新更新的数据,然后执行自定义测量操作。
        if (mAdapter != null) {
            mState.mItemCount = mAdapter.getItemCount();
        } else {
            mState.mItemCount = 0;
        }
        eatRequestLayout();
        mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
        resumeRequestLayout(false);
        mState.mInPreLayout = false; // clear
    }
}

RecyclerView的非自动化测绘流程也可以分为两部分:

第一部分:
在固定尺寸模式下,直接执行LayoutManager的onMeasure方法。

第二部分:
不在固定尺寸模式下

  1. 如果在测量的过程中有数据有更新,则先处理更新的数据
  2. 执行自定义测量流程,这需要自定义的LayoutManager实现onMeasure方法。

总结

一般情况下,进入的都是自动测量模式。

最不常见的是没有设置LayoutManager的模式。

最后需要注意的是:我们自定义LayoutManager的时候要根据自己的需求,决定是否要开启自动测量。如果需要开启,则要主动调用setAutoMeasureEnabled(true),否则默认是不开启的。

感谢您的阅读,如果您觉得本文对你有所帮助,请不要吝啬您的喜欢,您的喜欢是我写作的动力。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容