View测量流程

MeasureSpec

 

在开始进行理解View的测量流程之前,需要先理解MeasureSpec。

 

MeasureSpec代表的是32位的int值,它的高2位是SpecMode(也是一个int),低30位是SpecSize(也是一个int),SpecMode是测量模式,SpecSize是测量大小。

 

MeasureSpec相当于是两者的结合。系统封装了如何从MeasureSpec中提取SpecMode和SpecSize,也封装了用SpecMode和SpecSize组合成MeasureSpec。

public static class MeasureSpec {
    private static final int MODE_SHIFT = 30;
    private static final int MODE_MASK  = 0x3 << MODE_SHIFT;
    public static final int UNSPECIFIED = 0 << MODE_SHIFT;
    public static final int EXACTLY     = 1 << MODE_SHIFT;
    public static final int AT_MOST     = 2 << MODE_SHIFT;

    public static int makeMeasureSpec(int size,int mode) {
        if (sUseBrokenMakeMeasureSpec) {
            return size + mode;
        } else {
            return (size & ~MODE_MASK) | (mode & MODE_MASK);
        }
    }

    public static int getMode(int measureSpec) {
        return (measureSpec & MODE_MASK);
    }

    public static int getSize(int measureSpec) {
        return (measureSpec & ~MODE_MASK);
    }
}

 

SpecMode有3种,分别是

 

  • UNSPECIFIED 父容器对View不会有任何限制,要多大给多大,一般是用在系统内部使用,我们开发的APP用不到

     

  • EXACTLY 这种情况对应于match_parent和具体数值这两种模式,父容器已经检测出View需要的精确大小

     

  • AT_MOST 这种情况对应于wrap_content,父容器指定了一个最大值,View不能超过这个值

 

一般来说,View的MeasureSpec由父容器的MeasureSpec和自己的LayoutParams共同决定。因为有了MeasureSpec才可以在onMeasure中确定测量宽高。

 

下面是从源码中提取出来的摘要信息,后面会详细看源码分析,这里先提取出来

 

  • 如果View的宽高是固定的值,那么不管父容器的MeasureSpec是什么,View的MeasureSpec都是EXACTLY

     

  • 如果View的宽高是wrap_content,那么不管父容器的MeasureSpec是EXACTLY还是AT_MOST,最终View的MeasureSpec都是AT_MOST,这里暂时不用管UNSPECIFIED(我们用不到)。而且View最终的大小不能超过父容器的剩余空间

     

  • 如果View的宽高是match_parent,那么要分两种情况

    • 如果父容器是EXACTLY,那么View就是EXACTLY

    • 如果父容器是AT_MOST,那么View也是AT_MOST

 

这里不得不引用一张刚哥书籍里面的经典表格来表示一下。

从performMeasure()开始View测量

 

我们从ViewRootImpl的performTraversals()开始着手,仔细观察

private void performTraversals() {
    //host为根视图,即DecorView
    //desiredWindowWidth是Window的宽度
    measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);
}

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
                                     final Resources res, final int desiredWindowWidth,                                     final int desiredWindowHeight) {
    //分析1 : desiredWindowWidth就是Window的宽度,desiredWindowHeight是Window的高度
    childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
    childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
    //分析2 : 开始测量流程
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);                     
}


 

measure是从上往下执行的,widthMeasureSpec和heightMeasureSpec通常情况下是由父容器传递给子视图的。但是最外层的根视图,怎么拿到MeasureSpec呢?在执行performMeasure方法之前,我们需要拿到最外层的视图的MeasureSpec,看代码

private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
    //如果是MATCH_PARENT,那么就是EXACTLY
    case ViewGroup.LayoutParams.MATCH_PARENT:
        // Window can't resize. Force root view to be windowSize.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    //如果是WRAP_CONTENT,就是AT_MOST
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        // Window can resize. Set max size for root view.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        //如果是固定的值,也是EXACTLY
        // Window wants to be an exact size. Force root view to be that size.
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

 

最外层的根视图的MeasureSpec只由自己的LayoutParams决定,做自己的主人,舒服。

既然我们根视图拿到了MeasureSpec,接下来就要拿自己的MeasureSpec教孩子做人了。

​
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    switch (rootDimension) {
    //如果是MATCH_PARENT,那么就是EXACTLY
    case ViewGroup.LayoutParams.MATCH_PARENT:
        // Window can't resize. Force root view to be windowSize.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
        break;
    //如果是WRAP_CONTENT,就是AT_MOST
    case ViewGroup.LayoutParams.WRAP_CONTENT:
        // Window can resize. Set max size for root view.
        measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
        break;
    default:
        //如果是固定的值,也是EXACTLY
        // Window wants to be an exact size. Force root view to be that size.
        measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
        break;
    }
    return measureSpec;
}

​

 

measure方法里面有一些检测是否需要重新onMeasure的代码,被我略去了。

 

onMeasure是View里面的方法,ViewGroup是一个抽象类并且没有重写onMeasure。因为onMeasure方法的实现,每个都是不一样的,比如LinearLayout和FrameLayout的onMeasure方法肯定是实现逻辑不一样的。

 

因为DecorView是FrameLayout,所以我们看看FrameLayout中的onMeasure.

FrameLayout->onMeasure()

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            //分析1 : 遍历所有子控件,测量每个子控件的大小
                //参数1:View控件
                //参数2:宽MeasureSpec
                //参数3:父容器在宽度上已经用了多少了,因为FrameLayout的规则是:前面已经放置的View并不会影响后面放置View的宽高,是直接覆盖到上一个View上的.所以这里传0
                //参数4:高MeasureSpec
                //参数5:父容器在高度上已经用了多少了
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
        }
    }
    ......

    //分析2 : 测量完所有的子控件的大小之后,才知道自己的大小  这很符合FrameLayout的规则嘛
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
            resolveSizeAndState(maxHeight, heightMeasureSpec,
                    childState << MEASURED_HEIGHT_STATE_SHIFT));
    ......
}

FrameLayout的onMeasure方法中会遍历所有子控件,然后进行所有子控件的大小测量。最后才来设置自己的大小。注意,onMeasure方法的入参MeasureSpec是从父容器传过来的,意思就是给你个参考,你自己看着办吧。

 

在测量子控件大小的时候会调用ViewGroup的measureChildWithMargins方法,下面是代码:

protected void measureChildWithMargins(View child,
        int parentWidthMeasureSpec, int widthUsed,
        int parentHeightMeasureSpec, int heightUsed) {
    //获取子控件的LayoutParams
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

    //分析1: 计算子控件在宽上的MeasureSpec  
        //参数1:父容器的MeasureSpec
        //参数2:这里官方的入参名称是padding,从下面这个传值的形式来看,显然是子控件在宽上不能利用的空间(ViewGroup的左右两边padding+子控件的左右margin+父容器在宽度上已经使用了并且不能再使用的空间)
        //参数3:子控件想要的宽度
    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);

    //分析2: 将measure过程传递给子控件  如果子控件又是一个ViewGroup,那么继续向下传递
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

 

在measureChildWithMargins方法里我们首先是看到根据子控件的LayoutParams和父容器的MeasureSpec计算子控件的MeasureSpec,然后将计算出的MeasureSpec通过子控件的measure方法传递下去。如果子控件又是一个ViewGroup,那么它又会重复的measure流程,一直向下传递这个过程,直接最后的那个是View为止。因为View没有子控件,它就不能向下传递了。

 

所以我们自定义View(这里指那种直接继承自View)的时候,在onMeasure方法里面,需要根据自身的LayoutParams+父容器的MeasureSpec来计算SpecSize和SpecMode,最后根据业务场景来确定自己的大小(调用setMeasuredDimension来确定大小)。

 

注意了,接下来的getChildMeasureSpec方法就比较重要了。

 

这段代码对应着下面这段总结

 

  • 如果View的宽高是固定的值,那么不管父容器的MeasureSpec是什么,View的MeasureSpec都是EXACTLY

     

     

  • 如果View的宽高是wrap_content,那么不管父容器的MeasureSpec是EXACTLY还是AT_MOST,最终View的MeasureSpec都是AT_MOST,这里暂时不用管UNSPECIFIED(我们用不到)。而且View最终的大小不能超过父容器的剩余空间

     

  • 如果View的宽高是match_parent,那么要分两种情况

    • 如果父容器是EXACTLY,那么View就是EXACTLY

    • 如果父容器是AT_MOST,那么View也是AT_MOST

 

这段代码对应着上面刚哥总结的那个表格。同时也是measure流程的核心内容。对应刚哥的表格

 

因为在measureChildWithMargins方法里我们已经计算出子控件的MeasureSpec,然后通过measure传递给子控件了,如果子控件又是一个ViewGroup,那么它又会重复的measure流程,一直向下传递这个过程,直接最后的那个是View为止。因为View没有子控件,它就不能向下传递了。到这里其实我们的View的measure流程已经走完了,哈哈,不知不觉。

 

下面简单画一个流程图,方便理解上面的流程。

 

 

 

measure小结

 

从ViewRootImpl的performTraversals方法开始进入View的绘制过程,performTraversals方法里面会有一个performMeasure方法。这个performMeasure方法是专门拿来测量View的大小的。而且会遍历整个View树,全部进行测量。

 

在performMeasure里面会调用measure方法,然后measure会调用onMeasure方法,而在onMeasure方法中则会对所有的子元素进行measure过程。这相当于完成了一次从父元素到子元素的measure传递过程,如果子元素是一个ViewGroup,那么继续向下传递,直到所有的View都已测量完成。测量完成之后,我们可以根据getMeasureHeight和getMeasureWidth方法获取该View的高度和宽度。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
4S店客户管理小程序-毕业设计,基于微信小程序+SSM+MySql开发,源码+数据库+论文答辩+毕业论文+视频演示 社会的发展和科学技术的进步,互联网技术越来越受欢迎。手机也逐渐受到广大人民群众的喜爱,也逐渐进入了每个用户的使用。手机具有便利性,速度快,效率高,成本低等优点。 因此,构建符合自己要求的操作系统是非常有意义的。 本文从管理员、用户的功能要求出发,4S店客户管理系统中的功能模块主要是实现管理员服务端;首页、个人中心、用户管理、门店管理、车展管理、汽车品牌管理、新闻头条管理、预约试驾管理、我的收藏管理、系统管理,用户客户端:首页、车展、新闻头条、我的。门店客户端:首页、车展、新闻头条、我的经过认真细致的研究,精心准备和规划,最后测试成功,系统可以正常使用。分析功能调整与4S店客户管理系统实现的实际需求相结合,讨论了微信开发者技术与后台结合java语言和MySQL数据库开发4S店客户管理系统的使用。 关键字:4S店客户管理系统小程序 微信开发者 Java技术 MySQL数据库 软件的功能: 1、开发实现4S店客户管理系统的整个系统程序; 2、管理员服务端;首页、个人中心、用户管理、门店管理、车展管理、汽车品牌管理、新闻头条管理、预约试驾管理、我的收藏管理、系统管理等。 3、用户客户端:首页、车展、新闻头条、我的 4、门店客户端:首页、车展、新闻头条、我的等相应操作; 5、基础数据管理:实现系统基本信息的添加、修改及删除等操作,并且根据需求进行交流信息的查看及回复相应操作。
现代经济快节奏发展以及不断完善升级的信息化技术,让传统数据信息的管理升级为软件存储,归纳,集中处理数据信息的管理方式。本微信小程序医院挂号预约系统就是在这样的大环境下诞生,其可以帮助管理者在短时间内处理完毕庞大的数据信息,使用这种软件工具可以帮助管理人员提高事务处理效率,达到事半功倍的效果。此微信小程序医院挂号预约系统利用当下成熟完善的SSM框架,使用跨平台的可开发大型商业网站的Java语言,以及最受欢迎的RDBMS应用软件之一的MySQL数据库进行程序开发。微信小程序医院挂号预约系统有管理员,用户两个角色。管理员功能有个人中心,用户管理,医生信息管理,医院信息管理,科室信息管理,预约信息管理,预约取消管理,留言板,系统管理。微信小程序用户可以注册登录,查看医院信息,查看医生信息,查看公告资讯,在科室信息里面进行预约,也可以取消预约。微信小程序医院挂号预约系统的开发根据操作人员需要设计的界面简洁美观,在功能模块布局上跟同类型网站保持一致,程序在实现基本要求功能时,也为数据信息面临的安全问题提供了一些实用的解决方案。可以说该程序在帮助管理者高效率地处理工作事务的同时,也实现了数据信息的整体化,规范化与自动化。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值