Android自定义ViewGroup:如何理解和利用onMeasure

2018-08-06
在Android自定义开发ViewGroup时 总是避免不了对onMeasure方法的重写
那对这个方法应该如何理解?如何重写?有什么作用?等疑问接踵而来 这篇文章就来简洁地说明下这两个方法的使用

onMeasure此方法主要有两个使用目的
1.为本ViewGroup中所有的子view调用"它们自己的测量方法":View.measure(int,int)
2.在所有的子view的测量方法调用完成后 理论上所有子view都可以使用getMeasureWidth或getMeasureHeight获取测量宽度和高度 这时由此来确定ViewGroup自己的宽度和高度

现在我们通过阐述这两个目的来间接学习onMeasure的使用

1.调用"它们自己的测量方法":View.measure(int,int)
我们要先从子view测量方法开始说起
这个测量方式要情况而定 一般最基本也最通用的做法是使用Android已经封装好了的测量方法:

measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec)
measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed)

这两个方法都可以用于单个view的测量:

    for (int i = 0; i < getChildCount(); i++) {
        View view = getChildAt(i);
        if (view.getVisibility() != View.GONE) {
            measureChildWithMargins(view, widthMeasureSpec, 0, heightMeasureSpec, 0);
        }
    }

在这段for循环完成后view便有了自己的宽高 可以使用getMeasureWidth或getMeasureHeight获取测量宽度和高度了

注1:
这两个方法有一定的区别 只有measureChildWithMargins()在测量时会将子view的margin属性考虑进去 在margin会影响子view宽高时(如 子view设置宽度match_parent 并设置了marginLeft为10dp 那么最后获取到的view的测量宽度应该比父控件宽度小10dp)会影响其测量宽高 而measureChild不会 所以有时会造成测量不准的问题

注2:measureChildWithMargins()中那两个被赋值为0的两个参数 在android说明中说是为了在平行 or 垂直布局中设置固定间隔而用的 在代码中其实是直接加在了margin和padding值里 其实算是当作另一个margin来用 详细请查看源码

measureChildren(int widthMeasureSpec, int heightMeasureSpec)

方法源码:

protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {
    final int size = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
            measureChild(child, widthMeasureSpec, heightMeasureSpec);
        }
    }
}

没错 这个方法其实就是我们之前写的for循环子view进行测量的那部分代码 android早就给了封装方法 在实际开发中 直接使用这个方法基本就可以解决所有的测量问题
只是它用的是measureChild进行的测量 所以在子view使用了margin的情况下 有可能会影响实际测量值(一般只在子view设置为match_parent时有这种情况 所以也不是很大的问题) 必须在onLayout中考虑到这点

在调用了以上方法后 经过一系列计算 最终view.measure(int width,int height)会被调用 传入的就是view的测量高度和宽度 具体内容可看源码(实际和ViewGroup的onMeasure类似甚至更简单些 明白了onMeasure的用法 就很容易理解了)


2.确定ViewGroup自己的宽度和高度
这个问题要从ViewGroup的测量方法onMeasure(int widthMeasureSpec, int heightMeasureSpec)的参数意义开始说起
这个方法中的两个形参是两个int类型 但是它们不是单纯的数字 没有单纯的算术意义 而是记录了"测量模式"和测量高/宽度的数字(因为int类型共32位 前2位用来表示模式 后30位用来表示宽度或高度 具体可百度了解)
android提供了MeasureSpec工具类 方便我们提取测量模式和宽高:

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int width = MeasureSpec.getSize(widthMeasureSpec);//获取宽度
    int height = MeasureSpec.getSize(heightMeasureSpec);//获取高度

    int modeW = MeasureSpec.getMode(widthMeasureSpec);//获取宽度测量模式
    int modeH = MeasureSpec.getMode(heightMeasureSpec);//获取高度测量模式

    for (int i = 0; i < getChildCount(); i++) {
        View view = getChildAt(i);
        if (view.getVisibility() != View.GONE) {
            measureChildWithMargins(view, widthMeasureSpec, 0, heightMeasureSpec, 0);
        }
    }

    switch (modeW) {//判断宽度模式以演示
        case MeasureSpec.AT_MOST://当本控件在xml中宽度上写入 wrap_content时为此模式 这时返回的width一般为0
        case MeasureSpec.EXACTLY://当本控件在xml中宽度上写入实际值时为此模式 如20dp match_parent(与父布局一样的数值) 这里返回的width也就是xml中写明的数值
        case MeasureSpec.UNSPECIFIED://只有在ListView或类似的控件中会出现 表示不关心大小 这时返回的width一般为0
            break;
    }}

关键点在于如何去理解不同的测量模式给我们对确定ViewGroup宽高的影响
首先说明 确定了ViewGroup的宽高后 应该使用
setMeasuredDimension(int width, int height);
进行赋值 传入的两个Int是确定的数值

如何使用测量模式:
如 模式为 MeasureSpec.EXACTLY:
此时情况最简单 因为此模式下表示开发者使用XML传入了一个固定的值 我们直接设置为ViewGroup的宽高就可以了
setMeasuredDimension(width, height);

如模式为 MeasureSpec.AT_MOST:
此时情况为 XML要求宽高为wrap_content 也就是包裹内容 因为我们已经通过for测量了所有子view的宽高
所以我们可以这样:

    int maxHeight = 0;
    int maxWidth = 0;
    for (int i = 0; i < getChildCount(); i++) {
        View view = getChildAt(i);
        maxHeight = view.getMeasuredHeight() > maxHeight ? view.getMeasuredHeight() : maxHeight;
        maxWidth = view.getMeasuredWidth() > maxWidth ? view.getMeasuredWidth() : maxWidth;
    }
    setMeasuredDimension(maxWidth,maxHeight);

也可以这样:

    int maxHeight = 0;
    int maxWdith = 500;
    for (int i = 0; i < getChildCount(); i++) {
        View view = getChildAt(i);
        maxHeight += view.getMeasuredHeight();
    }
    setMeasuredDimension(maxWidth,maxHeight);

上述前者部分是view没有排布规则(如没有任何其它设置的FrameLayout) 我们取所以子view里最宽和最高的值作为我们布局的宽高 很符合"包裹内容"的定义

后者部分是view有垂直排布规则(如LinearLayout) 先假设宽度一定 因为子view垂直排布 所以 我们viewGroup的高度应该是所以子view的高度的和

至于MeasureSpec.UNSPECIFIED模式
一般会出现这个模式的情况是作为listView的item 这时应该开发者根据实际情况处理(一般直接按包裹内容方式处理)

到这里应该就可以看出 测量方法的重要性 不但是设置子view宽高的方法(子view在调用了测量方法后才会有测量宽高 之前会一直为0 没有经过这个方法也就无法完成view的绘制和排布) 同时也是为了确保控件在"自定义排布功能"的情况下确定自身宽高的方法

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

推荐阅读更多精彩内容