View的测量过程

简述

android的界面显示其实分成两块,一块了系统的DecorView(顶级view)和普通view ,而DecorView包含一个竖直的LinearLayout,上部分是titleBar ,下部分是一个id为content的frameLayout,不管是显示过程还是事件分发,都是由它传递而来。另一块就是我们自己设置填充到DecorView中framelayout的view 。现在是不是有点体会了,为什么在给activity设置布局的时候方法是setContentView() ,因为我们的确是把view设置到id为content的FrameLayout当中的。

不管是DecorView或者是普通view ,要显示到界面上都要经历三个过程:measure(测量宽高)layout(布局位置)draw(绘制)


view的测量过程

View的测量过程决定了它的宽高,MeasureSpec参与了view的测量,所以我们有必要详细了解一下

MeasureSpec

MeasureSpec代表了一个32位的int值,高2位是SpecMode(测量模式),低30位是测量SpecSize(测量尺寸),而SpecMode又分为三种,不同的模式下最终生成的尺寸是不一样的

  • AT_MOST (最大模式):父容器指定一个可用的大小即SpecSize,子view的大小不可超过该尺寸,具体视图子view的实现而定,对应于wrap_content属性

  • EXACTLY (精准模式):父容器已经计算出了确定的值,子view的最终大小就是SpecSize的值,对应于match_parent和确定值

  • UNSPECIFIED (未指定):父容器不对子view做任何限制,需要多大就给多大,一般用于系统内部,我们就不用太过关心了


现在我们大概了解了什么是MeasureSpec,那么它是怎样生成的呢?

对于DecorView,它的MeasureSpec由屏幕尺寸和自身的LayoutParamas决定。对于普通view,它的measure过程由ViewGroup的measureChild()调用

protected void measureChild(View child, int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed) {   
  final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();    
  final int childWidthMeasureSpec=getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin  + widthUsed,  lp.width);   
  final int childHeightMeasureSpec=getChildMeasureSpec(parentHeightMeasureSpec+mPaddingTop + mPaddingBottom + lp.topMargin +lp.bottomMargin+heightUsed, lp.height);   
  child.measure(childWidthMeasureSpec, childHeightMeasureSpec);}

由此可见,子view的measureSpec由父容器的MeasureSpec、padding/margin、自身layoutParamas决定,具体实现由于代码稍多就不贴出来了,有兴趣可以自己研究。总结起来就是:

  • 子view的宽高是具体值,SpecMode总是EXACTLY,最后的宽高就是设置的值

  • 子view的宽高为match_parent,则SpecMode(测量模式)由父容器决定,最后的宽高取决于父容器的测量尺寸

  • 子view的宽高为wrap_content,则SpecMode(测量模式)始终是AT_MOST,最后的宽高不能超过父容器的剩余空间

View的measure过程

view的measure()方法是一个final方法,里面调用的是onMeasure(),如下:

setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec), 
    getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));

setMeasureDimension()会把view的宽高测量设置进系统,再来看看系统默认的处理测量的宽高的方法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;
    //在我们关心的这2中模式下,返回的其实就是测量值
    case MeasureSpec.AT_MOST:
    case MeasureSpec.EXACTLY:
        result = specSize;
        break;
    }
    return result;
}

这个方法的逻辑也很简单,简单来说就是直接返回了测量值(SpecSize),当然这是系统默认的处理,我们在自定义view的时候可以重写onMeasure()方法,根据自己的逻辑把测量值设置进去。

额外补充一点,如果我们自定义view的时候默认系统设置测量值的方法,那么wrap_content的作用效果跟match_parent一样,为什么?回看一下上面的总结,当view的宽高属性值为wrap_content时,测量模式为AT_MOST,测量尺寸为specSize,就是父容器的剩余可用尺寸,这不就跟match_parent效果一样了么!

那有什么方法能处理么?很简单,重写onMeasure(),当宽高属性为wrap_content的时候,给设置一个默认的大小,ok搞定

@Overrideprotected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        //获取宽高的测量模式
    int withSpecMode = MeasureSpec.getMode(widthMeasureSpec);
    int withSpecSize = MeasureSpec.getSize(widthMeasureSpec);
    int heightSpecMode = MeasureSpec.getMode(heightMeasureSpec);
    int heightSpecSize = MeasureSpec.getSize(heightMeasureSpec);
        //当宽/高为wrap_content即对用AT_MOST的时候,设置一个默认值给系统    
    if (withSpecMode==MeasureSpec.AT_MOST && heightSpecMode==MeasureSpec.AT_MOST){
        setMeasuredDimension(defWith,defHeight);
    }else if (withSpecMode==MeasureSpec.AT_MOST){ 
       setMeasuredDimension(defWith,heightSpecSize);
    }else {
        setMeasuredDimension(withSpecSize,defHeight);
    }
}

ViewGroup的measure过程

对于ViewGroup本身,系统并没有提供一个默认的onMeasure方法,因为不同特性的ViewGroup(比如LinearLayoutRelativeLayout)他们的测量方式肯定是不一样的,所以需要子类自己去实现。而对于ViewGroup里面的view,则会通过measureChildren()循环遍历每一个view,然后调用measureChild()(这个方法的逻辑跟measureChildWithMargins()一模一样),如此从而完成测量。

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);
        }
    }
}

measureChild()方法的核心是:取出子view的layoutParamas和自身的MeasureSpec生成MeasureSpec传递给子view的measure()方法,完成对子view的测量

获取View的测量宽/高

measure()方法完成后,可以通过getMeasureHeight/with获取测量的宽高,但是这时取出的值并不一定是最终的值,某些情况下系统会多次调用measure才能完成测量。所以最准确的方式是在layout中获取测量的宽高值


如果我们想在activity或者fragment中获取一个view的测量宽高怎么办?

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

推荐阅读更多精彩内容