Android View 框架(2)-- measure

本篇你将了解到:

  1. LayoutParamsMeasureSpec 的作用和使用场景
  2. 父 View 如何使用 MeasureSpec 影响子 View 的测量
  3. 重写 onMeasure 的作用

View 的绘制过程,主要体现在 onMeasure()onLayout()onDraw() 这三个方法,这三部对应着一个 View 的测量,布局,绘制三个步骤。

在讲 onMeasure() 方法之前,我们先讲一下 Android 中,测绘中常用的参数类 -- LayoutParamsMeasureSpec

LayoutParams

LayoutParams 类只简单的描述了宽高,他们往往被设置成以下这三种值:

  1. 一个确定的值;
  2. FILL_PARENT,即填满父容器;
  3. WRAP_CONTENT,刚刚好包裹组件;

常见的使用方式如下:


// FrameLayout 的子 View 
FrameLayout.LayoutParams lytp = new FrameLayout.LayoutParams(80, LayoutParams.WRAP_CONTENT);
lytp .gravity = Gravity.CENTER;
btn.setLayoutParams(lytp);


// RelativeLayout 的子 View
RelativeLayout.LayoutParams lp = new RelativeLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); 
lp.addRule(RelativeLayout.ALIGN_PARENT_RIGHT, RelativeLayout.TRUE); 
lp.addRule(RelativeLayout.ALIGN_PARENT_TOP, RelativeLayout.TRUE); 
btn1.setLayoutParams(lp);

MeasureSpec

MeasureSpec 是父控件提供给子 View 的一个参数,作为设定自身大小参考,而子 View 也可以完全不参照这个值,设定自己的大小。

MeasureSpecsizemode 构成,size 代表着子 View 当前所在父布局的具体尺寸,其中 mode 包括以下三种:

  1. EXACTLY:父布局为子 View 指定确切大小,希望子 View 完全按照自己给定尺寸来处理。
  2. AT_MOST:父布局为子元素指定最大参考尺寸,希望子 View 的尺寸不要超过这个尺寸。
  3. UNSPECIFIED:父布局对子控件不加任何束缚,这种情况一般出现在 ScrollView 这种不限制大小的特殊父控件。

ViewGroup 如何测量子 View

看源码:

// 测量子 View
protected void measureChild(View child, int parentWidthMeasureSpec,
        int parentHeightMeasureSpec) {
    // 获取 child 的 LayoutParams
    final LayoutParams lp = child.getLayoutParams();

    final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
            mPaddingLeft + mPaddingRight, lp.width);
    final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
            mPaddingTop + mPaddingBottom, lp.height);

    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

/* 获取子 View 的 MeasureSpec
 * @param spec 子 View MeasureSpec
 * @param padding 当前 View 的 padding 大小,需要被减去
 * @param childDimension 子 View LayoutParams 的height,width
 */
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);
    // 父 View 推荐的 Size 需要留有一部给 padding
    int size = Math.max(0, specSize - padding);

    int resultSize = 0;
    int resultMode = 0;

    switch (specMode) {
    // Parent has imposed an exact size on us
    case MeasureSpec.EXACTLY:
        if (childDimension >= 0) {
            resultSize = childDimension;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.MATCH_PARENT) {
            // Child wants to be our size. So be it.
            resultSize = size;
            resultMode = MeasureSpec.EXACTLY;
        } else if (childDimension == LayoutParams.WRAP_CONTENT) {
            // Child wants to determine its own size. It can't be
            // bigger than us.
            resultSize = size;
            resultMode = MeasureSpec.AT_MOST;
        }
        break;
        ...
        // 省略了 MeasureSpec.AT_MOST 和 MeasureSpec.UNSPECIFIED
    }
}

看完源码后,大致能了解到测量是由父 View 的 MeasureSpec 和子 View 的 LayoutParams 一起决定的。更具体的对应关系如下:

  1. 子 View 指定大小值时:

    • Mode = EXACTLY
    • Size = 指定的大小
  2. 子View指定为 MATCH_PARENT 时:

    • Mode = 父View此时的模式
    • Size = 父View的大小 – padding
  3. 子View指定为 WRAP_CONTENT 时:

    • Mode = AT_MOST
    • Size = 父View的大小 – padding

根据以上的规律,父 View 一层一层的向下设置子 View 的 MeasureSpec。那么子 View 是如何根据父 View 给的 MeasureSpec,确定自身的大小呢?答案就在下面马上要讲的 onMeasure()方法里。

为什么要重写 onMeasure ?

调用 setMeasuredDimension 后,正式的确定了当前 View 的大小,那默认的onMeasure是怎么去调用的呢?

 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
            getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}
    
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;
    // AT_MOST 和 EXACTLY 使用同一种处理方式
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

View 的默认 onMeasure 方法中,可以看到 MeasureSpec.AT_MOSTMeasureSpec.EXACTLY 方法处理的方式是一样的。这样导致,自定义的 View 使用 WRAP_CONTENTMATCH_PARENT 的效果是一样的,都会撑满整个父容器。

在某些情况下,我们并不想要默认的那种效果,这时候我们就可以根据自己的需求,去重写 onMeasure 方法。
这里我们简单的重写一下,实现功能如下:

  1. 如果指定高度和宽度的大小,那么结果就是这个值;
  2. 如果设置为 MATCH_PARENT ,就填满父容器;
  3. 如果设置为 WRAP_CONTENT ,就在父容器建议值和本 View 设置的建议值取最小值。
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    setMeasuredDimension(doLocalMeasure(60, widthMeasureSpec),
            doLocalMeasure(30, heightMeasureSpec));
}

int doLocalMeasure(int defaultSize, int measureSpec) {
    int result = defaultSize;
    int mode = MeasureSpec.getMode(measureSpec);
    int size = MeasureSpec.getSize(measureSpec);
    switch (mode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.EXACTLY:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
            result = Math.min(result, size);
            break;
    }
    return result;
}

小结

这一章简单的讲了 View 的测量流程,以及重写 onMeasure 方法的作用。
下一篇:Android View 框架(3)-- layout

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