Measure要知道的点

1.measure和onMeasure

View中和测量过程相关的方法有三个,measure、onMeasure和setMeasuredDimension。


image

1.View与ViewGroup的不同

  • View的measure调用onMeasure.
  • View 中的onMeasure只调用了setMeasuredDimension来设置自身高度。
  • 在ViewGroup中,onMeasure方法并没有被重写法,所以继承ViewGroup自定义ViewGroup一定要重写onMeasure来测量子View,否则不会测量子View。
  • 在自定义View中,onMeasure方法是在一般情况下使用父容器提供的宽高,除了UNSPECIFIED情况下使用最小尺度。继承View的自定义要根据业务onMeasure要选择性重写

2.View的onMeasure

2.1 原生的onMeasure

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
2.1.1 getDefaultSize

其中获取宽高的主要方法是getDefaultSize

public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);

    switch (specMode) {
    case MeasureSpec.UNSPECIFIED:
        result = size;
        break;
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

当父容器传来的MeasureSpec为UNSPECIFIED时,数值取自己的最小值;为AT_MOST和EXACTLY时,取父容器传递的数值,也就是Wrap_Content和Match_Paraent

2.1.2 setMeasuredDimension

setMeasuredDimension方法是设置view宽高的方法,也是onMeasure必须要调用的方法

private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {
    mMeasuredWidth = measuredWidth;
    mMeasuredHeight = measuredHeight;

    mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

setMeasuredDimensionRaw是最终被调用的方法,保存宽高,修改标识符。

如果在onMeasure中没有调用setMeasuredDimension来设置宽高,在measure中,调用onMeasure后,会通过mPrivateFlags来判断是否设置了宽高,没有设置就会抛出异常。所以说setMeasuredDimension是必须要在onMeasure中被调用

2.2 自定义View重写onMeasure

从getDefaultSize方法实现来看,直接继承View的自定义控件要重写onMeasure方法并设置wrap_content时的自身大小,否则在布局中使用wrap_content就相当于使用match_parent。

使用 resolveSize() 来让子 View 的计算结果符合父 View 的限制(当然,如果你想用自己的方式来满足父 View 的限制也行)。

public static int resolveSize(int size, int measureSpec) {
    return resolveSizeAndState(size, measureSpec, 0) & MEASURED_SIZE_MASK;
}
public static int resolveSizeAndState(int size, int measureSpec, int childMeasuredState) {
    final int specMode = MeasureSpec.getMode(measureSpec);
    final int specSize = MeasureSpec.getSize(measureSpec);
    final int result;
    switch (specMode) {
        case MeasureSpec.AT_MOST:
            if (specSize < size) {
            //标记量算完的尺寸没有达到了View想要的宽度
                result = specSize | MEASURED_STATE_TOO_SMALL;
            } else {
                result = size;
            }
            break;
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        case MeasureSpec.UNSPECIFIED:
        default:
            result = size;
    }
    return result | (childMeasuredState & MEASURED_STATE_MASK);
}

相比原生的的onMeasure中的getDefaultSize,resolveSize对于Wrap_Content情况有了特别处理,在不大于父容器要求的尺寸下,使用用户自己规定的尺寸。

上面的result就是保存的measureSpec的mode值,唯一不同的是第31位被用于标示尺寸是不是达到了View想要的宽度,如果不满足,则标为1。下面把2位标示直观表示下:

UNSPECIFIED 00
EXACTLY     01
AT_MOST     10
result = AT_MOST | MEASURED_STATE_TOO_SMALL  11

resolveSizeAndState的结果在resolveSize进行了截取,只取了数值部分,用来标记量算完的尺寸是不是达到了View想要的宽度的高八位没有保留。

public final int getMeasuredWidth() {
    return mMeasuredWidth & MEASURED_SIZE_MASK;
}
public final int getMeasuredWidthAndState() {
    return mMeasuredWidth;
}

而getMeasuredWidth和getMeasuredWidthAndState的区别也在此,getMeasuredWidth是获取存数值,而getMeasuredWidthAndState还带有高位state。

线性布局中就用了resolveSizeAndState来获取宽高。自己使用时可配合父容器使用。

3.ViewGroup的onMeasure

虽然ViewGroup没有实现omMeasure的过程,但是它提供了两个工具方法:measureChildren()和getChildMeasureSpec()。

我们都知道父容器会调用子View的measure方法来测量子View,那传入的参数int widthMeasureSpec, int heightMeasureSpec是怎么来的呢?

3.1 理解MeasureSpec

系统会将View的LayoutParams根据父容器所施加的规则转成对应的MeasureSpec,然后再根据这个MeasureSpec来测量出View的宽和高。

MeasureSpec代表一个32位的int值,高2位代表SpecMode,低30位代表SpecSize,SpecMode是指测量模式,而SpecSize是指在某种测量模式下的规格大小。

SpecMode:UNSPECIFIED,EXACTLY, AT_MOST(wrap_content)

UNSPECIFIED 00
EXACTLY     01
AT_MOST     10
MeasureSpec.makeMeasureSpec(childSpecSize, childSpecMode);

通过上面这个方法将尺寸和mode拼接成MeasureSpec。

private static final int MODE_SHIFT = 30;
private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
public static int makeMeasureSpec(@IntRange(from = 0, to = (1 << MeasureSpec.MODE_SHIFT) - 1) int size,
                                  @MeasureSpecMode int mode) {
    if (sUseBrokenMakeMeasureSpec) {
        return size + mode;
    } else {
        return (size & ~MODE_MASK) | (mode & MODE_MASK);
    }
}

可以看出,MODE_MASK就是32位int值,高两位为1,通过与MODE_MASK的与或来获取mode和size再拼接。

其实View中的MEASURED_SIZE_MASK = ~MODE_MASK,MEASURED_STATE_MASK = MODE_MASK;

public static final int MEASURED_SIZE_MASK = 0x00ffffff;
public static final int MEASURED_STATE_MASK = 0xff000000;

3.2 ViewGroup的getChildMeasureSpec

getChildMeasureSpec就是获取要传递给子View的MeasureSpec的方法,要传给子View的MeasureSpec就是通过这个获取的。它通过
MeasureSpec与子View的宽度来获取值。

public static final int MATCH_PARENT = -1;
public static final int WRAP_CONTENT = -2;

所以在LayoutParams中,设置了固定长度(有意义)的View宽高是不为负数的。

3.2.1 子View设置了layout_width(height)
if (childDimension >= 0) {
    //子类自己决定size
    resultSize = childDimension;
    resultMode = MeasureSpec.EXACTLY;
}

无论哪种specMode,对于设置了精确长度的子View,取设置的宽高,且mode为EXACTLY。

3.2.2 layout_width(height)="wrap_content"
if (childDimension == LayoutParams.WRAP_CONTENT) {
    // 子类自己决定size,但不能超过父类
    resultSize = size;
    resultMode = MeasureSpec.AT_MOST;
}

除了specMode为UNSPECIFIED,子View为wrap_content时,取得宽高为方法参数中的尺寸(例如LinearLayout中为其父容器测量传递的MeasureSpec),mode为AT_MOST。

3.2.3 layout_width(height)="match_content"

不考虑UNSPECIFIED

switch (specMode) {
    // Parent has imposed an exact size on us
    case MeasureSpec.EXACTLY:
        if (childDimension == LayoutParams.MATCH_PARENT) {
            // 等于父容器的宽度
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        }
        break;
    // Parent has imposed a maximum size on us
    case MeasureSpec.AT_MOST:
        if (childDimension == LayoutParams.MATCH_PARENT) {
            // 子View想和父容器尺寸一样,但父容器自己也还没确定尺寸
            resultSize = size;
            // 子View尺寸肯定不能大于父容器
            resultMode = MeasureSpec.AT_MOST;
        }

父容器如果有确定的size,那子View就和父View一样;如果没有确定尺寸,那测量子View的MeasureSpec和父容器一样。

4 参考

源码解析Android中View的measure量算过程

一篇文章理解Android 视图树的测量过程

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

推荐阅读更多精彩内容