Android中mesure过程详解 –[通俗易懂]

Android中mesure过程详解 –[通俗易懂]invalidate()最后会发起一个View树遍历的请求,并通过执行performTraersal()来响应该请求,performTraersal()正是对View树进行遍历和绘制的核心函数,内部的

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE使用 1年只要46元 售后保障 童叟无欺

invalidate()最后会发起一个View树遍历的请求,并通过执行performTraersal()来响应该请求,performTraersal()正是对View树进行遍历和绘制的核心函数,内部的主体逻辑是判断是否需要重新测量视图大小(measure),是否需要重新布局(layout),是否重新需要绘制(draw)。measure过程是遍历的前提,只有measure后才能进行布局(layout)和绘制(draw),因为在layout的过程中需要用到measure过程中计算得到的每个View的测量大小,而draw过程需要layout确定每个view的位置才能进行绘制。下面我们主要来探讨一下measure的主要过程,相对与layout和draw,measure过程理解起来比较困难。

      我们在编写layout的xml文件时会碰到layout_width和layout_height两个属性,对于这两个属性我们有三种选择:赋值成具体的数值,match_parent或者wrap_content,而measure过程就是用来处理match_parent或者wrap_content,假如layout中规定所有View的layout_width和layout_height必须赋值成具体的数值,那么measure其实是没有必要的,但是google在设计Android的时候考虑加入match_parent或者wrap_content肯定是有原因的,它们会使得布局更加灵活。

      首先我们来看几个关键的函数和参数:

      1、public final void measue(int widthMeasureSpec, int heightMeasureSpec);

      2、protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec);

      3、protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec)

      4、protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec)

      5、protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec, int heightUsed)

     接着我们来看View类中measure和onMeasure函数的源码:

    

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        if ((mPrivateFlags & FORCE_LAYOUT) == FORCE_LAYOUT ||
                widthMeasureSpec != mOldWidthMeasureSpec ||
                heightMeasureSpec != mOldHeightMeasureSpec) {

            // first clears the measured dimension flag
            mPrivateFlags &= ~MEASURED_DIMENSION_SET;

            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_MEASURE);
            }

            // measure ourselves, this should set the measured dimension flag back
            onMeasure(widthMeasureSpec, heightMeasureSpec);

            // flag not set, setMeasuredDimension() was not invoked, we raise
            // an exception to warn the developer
            if ((mPrivateFlags & MEASURED_DIMENSION_SET) != MEASURED_DIMENSION_SET) {
                throw new IllegalStateException("onMeasure() did not set the"
                        + " measured dimension by calling"
                        + " setMeasuredDimension()");
            }

            mPrivateFlags |= LAYOUT_REQUIRED;
        }

        mOldWidthMeasureSpec = widthMeasureSpec;
        mOldHeightMeasureSpec = heightMeasureSpec;
    }

 

由于函数原型中有final字段,那么measure根本没打算被子类继承,也就是说measure的过程是固定的,而measure中调用了onMeasure函数,因此真正有变数的是onMeasure函数,onMeasure的默认实现很简单,源码如下:

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

onMeasure默认的实现仅仅调用了setMeasuredDimension,setMeasuredDimension函数是一个很关键的函数,它对View的成员变量mMeasuredWidth和mMeasuredHeight变量赋值,而measure的主要目的就是对View树中的每个View的mMeasuredWidth和mMeasuredHeight进行赋值,一旦这两个变量被赋值,则意味着该View的测量工作结束。

 

protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {
        mMeasuredWidth = measuredWidth;
        mMeasuredHeight = measuredHeight;

        mPrivateFlags |= MEASURED_DIMENSION_SET;
    }

 

对于非ViewGroup的View而言,通过调用上面默认的measure——>onMeasure,即可完成View的测量,当然你也可以重载onMeasure,并调用setMeasuredDimension来设置任意大小的布局,但一般不这么做,因为这种做法太“专政”,至于为何“专政”,读完本文就会明白

      对于ViewGroup的子类而言,往往会重载onMeasure函数负责其children的measure工作,重载时不要忘记调用setMeasuredDimension来设置自身的mMeasuredWidth和mMeasuredHeight。如果我们在layout的时候不需要依赖子视图的大小,那么不重载onMeasure也可以,但是必须重载onLayout来安排子视图的位置,这在下一篇博客中会介绍。  

      再来看下measue(int widthMeasureSpec, int heightMeasureSpec)中的两个参数, 这两个参数分别是父视图提供的测量规格,当父视图调用子视图的measure函数对子视图进行测量时,会传入这两个参数,通过这两个参数以及子视图本身的LayoutParams来共同决定子视图的测量规格,在ViewGroup的measureChildWithMargins函数中体现了这个过程,稍后会介绍。

     MeasureSpec参数的值为int型,分为高32位和低16为,高32位保存的是specMode,低16位表示specSize,specMode分三种:

      1、MeasureSpec.UNSPECIFIED,父视图不对子视图施加任何限制,子视图可以得到任意想要的大小;

      2、MeasureSpec.EXACTLY,父视图希望子视图的大小是specSize中指定的大小;

      3、MeasureSpec.AT_MOST,子视图的大小最多是specSize中的大小。

      以上施加的限制只是父视图“希望”子视图的大小按MeasureSpec中描述的那样,但是子视图的具体大小取决于多方面的。

      ViewGroup中定义了measureChildren, measureChild,  measureChildWithMargins来对子视图进行测量,measureChildren内部只是循环调用measureChild,measureChild和measureChildWithMargins的区别就是是否把margin和padding也作为子视图的大小,我们主要分析measureChildWithMargins的执行过程:

 

    protected void measureChildWithMargins(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);
    }

 

总的来看该函数就是对父视图提供的measureSpec参数进行了调整(结合自身的LayoutParams参数),然后再来调用child.measure()函数,具体通过函数getChildMeasureSpec来进行参数调整,过程如下:

public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        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;

        // Parent has imposed a maximum size on us
        case MeasureSpec.AT_MOST:
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } 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;

        // Parent asked to see how big we want to be
        case MeasureSpec.UNSPECIFIED:
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = 0;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

getChildMeasureSpec的总体思路就是通过其父视图提供的MeasureSpec参数得到specMode和specSize,并根据计算出来的specMode以及子视图的childDimension(layout_width和layout_height中定义的)来计算自身的measureSpec,如果其本身包含子视图,则计算出来的measureSpec将作为调用其子视图measure函数的参数,同时也作为自身调用setMeasuredDimension的参数,如果其不包含子视图则默认情况下最终会调用onMeasure的默认实现,并最终调用到setMeasuredDimension,而该函数的参数正是这里计算出来的。

 

      总结:从上面的描述看出,决定权最大的就是View的设计者,因为设计者可以通过调用setMeasuredDimension决定视图的最终大小,例如调用setMeasuredDimension(100, 100)将视图的mMeasuredWidth和mMeasuredHeight设置为100,100,那么父视图提供的大小以及程序员在xml中设置的layout_width和layout_height将完全不起作用,当然良好的设计一般会根据子视图的measureSpec来设置mMeasuredWidth和mMeasuredHeight的大小,已尊重程序员的意图。

 

 

 

 

 

 

 

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/166894.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)


相关推荐

  • sql注入orderby子句的功能_sql group by order by一起用

    sql注入orderby子句的功能_sql group by order by一起用uniqueidentifier全局唯一标识符(GUID)。注释uniqueidentifier数据类型的列或局部变量可用两种方法初始化为一个值:使用NEWID函数。将字符串常量转换为如下形式(xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx,其中

  • 十大经典排序算法-快速排序算法详解

    十大经典排序算法-快速排序算法详解一、什么是快速排序1.概念快速排序(QuickSort)是从冒泡排序算法演变而来的,实际上是在冒泡排序基础上的递归分治法。快速排序在每一轮挑选一个基准元素,并让其他比它大的元素移动到数列一边,比它小的元素移动到数列的另一边,从而把数列拆解成了两个部分2.算法原理这是一个无序数列:4、5、8、1、7、2、6、3,我们要将它按从小到大排序。按照快速排序的思想,我们先选择一个基准元素,进行排序我们选取4为我们的基准元素,并设置基准元素的位置为index,设置两个指针left和right,分别指向最左

  • 龙芯CPU架构_龙芯mips架构

    龙芯CPU架构_龙芯mips架构龙芯架构龙芯指令集:CPU访存指令24个全部来自MIPSCPU算数指令(ALU)10个全部来自MIPSCPU算数指令14个全部来自MIPSCPU乘除指令12个来自MIPS12个来自龙芯(其中8个重复MIPS指令功能)CPU跳转分支指令20个全部来自MIPSCPU位移指令15个全部来自MIPSCPU特殊指令2个全部来自MIPSCPU异常指令12个全…

  • G1垃圾收集器详解

    G1垃圾收集器详解G1垃圾收集器GC收集器的三个考量指标:占用的内存(Capacity)延迟(Latency)吞吐量(Throughput)随着硬件的成本越来越低,机器的内存也越来越大,GC收集器占用的内存基本上可以容忍。而吞吐量可以通过集群(增加机器)来解决。随着JVM中内存的增大,STW的时间成为JVM急迫解决的问题,如果还是按照传统的分代模型,使用传统的垃圾收集器,那么STW的时间将会越来越长。在传统的垃圾收集器中,STW的时间是无法预测的,有没有一种办法,能够首先定义一个停顿时间,然后反向推算收集内

  • service mesh框架对比_k8s containerd

    service mesh框架对比_k8s containerdservice用于4层路由负载 ingress用于7层路由负载1 Service介绍在kubernetes中,Pod是应用程序的载体,我们可以通过Pod的IP来访问应用程序,但是Pod的IP地址不是固定的,这就意味着不方便直接采用Pod的IP对服务进行访问。为了解决这个问题,kubernetes提供了Service资源,Service会对提供同一个服务的多个Pod进行聚合,并且提供一个统一的入口地址,通过访问Service的入口地址就能访问到后面的Pod服务。Service在很多情况下只是一个概念

  • PS2手柄-1「建议收藏」

    PS2手柄-1「建议收藏」相关定义Comd[2]={0x01,0x42};存储了两条指令码,分别是开始指令和请求数据指令。Data[9]={0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};数据存储数组,初始全为0。MASK[16]={PSB_SELECT,PSB_L3,PSB_R3,PSB_START,PSB_PAD_UP,PSB_PAD_RIGHT,PSB_PAD_DOWN,PSB_PAD_LEFT,PSB_L2,PSB_R2,PSB_L1,PSB_R1,PSB_GREEN,P

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号