2019独角兽企业重金招聘Python工程师标准>>>

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 flagmPrivateFlags &= ~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 developerif ((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 uscase 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 uscase MeasureSpec.AT_MOST:            if (childDimension >= 0) {                // Child wants a specific size... so be itresultSize = 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 becase MeasureSpec.UNSPECIFIED:            if (childDimension >= 0) {                // Child wants a specific size... let him have itresultSize = childDimension;resultMode = MeasureSpec.EXACTLY;} else if (childDimension == LayoutParams.MATCH_PARENT) {                // Child wants to be our size... find out how big it should                // beresultSize = 0;resultMode = MeasureSpec.UNSPECIFIED;} else if (childDimension == LayoutParams.WRAP_CONTENT) {                // Child wants to determine its own size.... find out how                // big it should beresultSize = 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的大小,已尊重程序员的意图。

转载于:https://my.oschina.net/u/2370693/blog/543119

Android中mesure过程详解相关推荐

  1. Android中mesure过程详解 (结合Android 4.0.4 最新源码)

    如何遍历并绘制View树?之前的文章Android中invalidate() 函数详解(结合Android 4.0.4 最新源码)中提到invalidate()最后会发起一个View树遍历的请求,并通 ...

  2. Android中layout过程详解 (结合Android 4.0.4 最新源码)

    上一篇文章Android中mesure过程详解 (结合Android 4.0.4 最新源码)介绍了View树的measure过程,相对与measure过程,本文介绍的layout过程要简单多了,正如l ...

  3. Android中layout过程详解

    相比较onMeasure ,layout过程要简单多了,正如layout的中文意思"布局"中表达的一样,layout的过程就是确定View在屏幕上显示的具体位置,在代码中就是设置其 ...

  4. android 按键用户点击事件,Android按键事件处理过程详解

    Android按键事件处理过程详解 (2013-09-26 14:05:19) 标签: it 在Android系统中,存在多种界面事件,如点击事件.触摸事件.焦点事件和菜单事件等,在这些界面事件发生时 ...

  5. Android 中malloc_debug 原理详解

    版本基于:Android R 关联博文: Android 中malloc_debug 使用详解 0. 前言 最近上项目中遇到一个native 可能内存泄漏的问题,曾考虑使用HWASAN,但这个工具是针 ...

  6. android调webview的方法,Android中的WebView详解

    Android中的WebView详解 WebView详解 基本用法 布局文件配置WebView android:id="@+id/wv_news_detail" android:l ...

  7. Android中shape属性详解

    一.简单使用 刚开始,就先不讲一堆标签的意义及用法,先简单看看shape标签怎么用. 1.新建shape文件 首先在res/drawable文件夹下,新建一个文件,命名为:shape_radius.x ...

  8. 简诉android源代码编译过程,详解Android源码的编译

    在这里我们将介绍的是Android源码的编译,主要基于Android 1.0环境下.希望对大家有所帮助. 本文将为大家介绍的是如何设置Android源码的编译环境,包括Linux下的配置.主要基于An ...

  9. android context继承关系,android中Context深入详解

    以下分别通过Context认知角度,继承关系,对象创建等方面android中Context做了深入的解释,一起学习下. 1.Context认知. Context译为场景,一个应用程序可以认为是一个工作 ...

最新文章

  1. 社交媒体分析-恶意内容自动检测相关论文
  2. JMeter3.0 post参数/BeanShell中文乱码问题
  3. MATLAB实战系列(十五)-在matlab中如何实现Octave工具包的全自动加载?
  4. linux系统外接硬盘_linux通过fstab自动挂载外接磁盘
  5. mysql npe问题_MySQL为Null会导致5个问题,个个致命!
  6. [新闻]MacBook Pro新版上市
  7. windows下手动安装composer并配置环境变量
  8. numpy实用技巧(二)
  9. js的tree数组对象扁平化思否_js 中的tree型数组如何去重与合并?
  10. 2本学计算机好就业吗,今年高三了,感觉考不上2本了,想读个好点的大专将来好就业。学什么技术好呢,汽修,数控,计算机还是别的什么,求支招,毕业5年后打算自己开店的。...
  11. 关于iptables封禁国外ip的方法
  12. 2021李宏毅机器学习、深度学习笔记
  13. 系统修复-菜鸟也DIY
  14. 达梦数据库大小写这个参数敏感怎样设置
  15. PyTorch深度学习快速入门教程(绝对通俗易懂!!!)
  16. 解决IE浏览器打印iframe页面时字体等样式缩小的问题
  17. 【QCM2150】WCN3680 WFA 11ac STA 5.2.1/5.2.61测试失败
  18. 《编译原理-龙书》练习第2章
  19. 用Springboot写饼状图
  20. 拦截导弹 导弹防御系统

热门文章

  1. 胡扯JS系列-匿名函数的自动运行
  2. PHP 7.3 我们将迎来灵活的 heredoc 和 nowdoc 句法结构
  3. 15.1-15.3 FTP,使用vsftpd搭建FTP服务
  4. bugzilla迁移需要修改的文件
  5. FoxPro 常用内部函数
  6. 判断是否是电脑访问网站 1号店页面判断脚本
  7. SSH生成rsa密钥对
  8. Tomcat7.0安装配置
  9. TweetBot TabBar
  10. wps office oa控件 痕迹_WPS大更新,Office的付费功能免费用,我不会是最后一个知道的吧?...