Measure 流程详解

  • 一、概述
  • 二、单一 View 的测量流程
    • 1. 流程图
    • 2. 源码分析
  • 三、ViewGroup 的测量流程
    • 1. 流程图
    • 2. 源码分析

一、概述

测量过程分为 View的measure过程 和 ViewGroup的measure过程。

View的类型 measure 过程
单一的View (如 ImageView) 只测量自身
ViewGroup 遍历测量该 ViewGroup 下的所有子 View

版本: Android SDK 29
关联文章:

  1. 《View系列 (一) — Android 坐标系》
  2. 《View系列 (二) — MeasureSpec 详解》
  3. 《View系列 (三) — Measure 流程详解》
  4. 《View系列 (四) — Layout 流程详解》
  5. 《View系列 (五) — Draw 流程详解》

二、单一 View 的测量流程

1. 流程图

2. 源码分析

ViewGroup.measureChildWithMargins()

// ViewGroup.class
protected void measureChildWithMargins(View child,int parentWidthMeasureSpec, int widthUsed,int parentHeightMeasureSpec, int heightUsed) {// 1.子View的LayoutParams参数final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();// 2.根据父容器的MeasureSpec和子View的LayoutParams来确认子View的MeasureSpec。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);// 3.将子View的MeasureSpec传递给子View,如果子View的单一View,方便其测量自身。child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

View.measure()

// View.class
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {// ...省略代码...int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);if (cacheIndex < 0 || sIgnoreMeasureCache) {// measure ourselves, this should set the measured dimension flag back// 这里执行单一View的onMeasure()方法onMeasure(widthMeasureSpec, heightMeasureSpec);mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;}// ...省略代码...
}protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}protected final void setMeasuredDimension(int measuredWidth, int measuredHeight) {// ...省略代码...// 赋值setMeasuredDimensionRaw(measuredWidth, measuredHeight);
}private void setMeasuredDimensionRaw(int measuredWidth, int measuredHeight) {// 将子View的大小赋值给下面两个字段mMeasuredWidth = measuredWidth;mMeasuredHeight = measuredHeight;mPrivateFlags |= PFLAG_MEASURED_DIMENSION_SET;
}

View.getSuggestedMinimumWidth()

// View.class
// 1.无背景,就返回mMinWidth,这个参数其实就是布局文件里的 android:minWidth 属性。
// 2.有背景,返回mMinWidth与背景宽度中的较大值。
protected int getSuggestedMinimumWidth() {return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
}// Drawable.class
public int getMinimumWidth() {// 如果背景有宽度,就返回宽度。final int intrinsicWidth = getIntrinsicWidth(); // getIntrinsicWidth()默认返回-1return intrinsicWidth > 0 ? intrinsicWidth : 0;
}

View.getDefaultSize()

// View.class
public static int getDefaultSize(int size, int measureSpec) {//参数 measureSpec 是子View的MeasureSpecint result = size;// 获取子View的LauoutParams参数和父容器协调后的尺寸。int specMode = MeasureSpec.getMode(measureSpec);int specSize = MeasureSpec.getSize(measureSpec);switch (specMode) {case MeasureSpec.UNSPECIFIED: //这里其实就是特殊处理一下UNSPECIFIED时,子View的大小。result = size;break;case MeasureSpec.AT_MOST:case MeasureSpec.EXACTLY:result = specSize;break;}return result;
}

三、ViewGroup 的测量流程

原理

  1. 遍历测量所有子View的尺寸大小。
  2. 合并所有子View的尺寸大小,最终得到ViewGroup父视图的测量值。

1. 流程图

2. 源码分析

// View.class
public final void measure(int widthMeasureSpec, int heightMeasureSpec) {// ...省略代码...int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);if (cacheIndex < 0 || sIgnoreMeasureCache) {// measure ourselves, this should set the measured dimension flag back// 这里执行单一View的onMeasure()方法onMeasure(widthMeasureSpec, heightMeasureSpec);mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;}// ...省略代码...
}

为什么ViewGroup 中没有重写 onMeasure() 方法,而是在各个继承自 ViewGroup 的容器控件中实现?

每个容器型控件的布局特性都不一样,所以无法对 onMeasure 进行统一处理。

在 ViewGroup 中,measureChildren() 方法 可以用来遍历测量子View。

// ViewGroup.class
protected void measureChildren(int widthMeasureSpec, int heightMeasureSpec) {final int size = mChildrenCount;final View[] children = mChildren;// 遍历当前ViewGroup内部的所有子Viewfor (int i = 0; i < size; ++i) {final View child = children[i];// 子View显示模式不为GONEif ((child.mViewFlags & VISIBILITY_MASK) != GONE) {// 测量子ViewmeasureChild(child, widthMeasureSpec, heightMeasureSpec);}}
}// 该方法与上面的 measureChildWithMargins()方法类似
protected void measureChild(View child, int parentWidthMeasureSpec,int parentHeightMeasureSpec) {// 1.子View的LayoutParams参数final LayoutParams lp = child.getLayoutParams();// 2.根据父容器的MeasureSpec和子View的LayoutParams来确认子View的MeasureSpec。final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,mPaddingLeft + mPaddingRight, lp.width);final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,mPaddingTop + mPaddingBottom, lp.height);// 3.将子View的MeasureSpec传递下去。child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

关于 onMeasure() 方法,我们以 LinearLayout.onMeasure() 为例进行分析。

// LinearLayout.class
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {if (mOrientation == VERTICAL) {measureVertical(widthMeasureSpec, heightMeasureSpec);} else {measureHorizontal(widthMeasureSpec, heightMeasureSpec);}
}// 竖直方向排列时,子View的高度相加,宽度取最宽的值。
void measureVertical(int widthMeasureSpec, int heightMeasureSpec) {mTotalLength = 0; // 该变量是保存该ViewGroup在竖直方向的总高度int maxWidth = 0; //记录最大宽度int childState = 0;float totalWeight = 0;// 获取垂直方向上的子View个数final int count = getVirtualChildCount();// ...省略代码...final int widthMode = MeasureSpec.getMode(widthMeasureSpec);final int heightMode = MeasureSpec.getMode(heightMeasureSpec);boolean skippedMeasure = false;// See how tall everyone is. Also remember max width.// 遍历子View并获取其宽/高,并记录下子View中最大的宽度值for (int i = 0; i < count; ++i) {// ...省略代码... View为null和可见性为GONE的View// ...省略添加分割线高度的逻辑final LayoutParams lp = (LayoutParams) child.getLayoutParams();// 记录子View是否有weight属性设置,用于后面判断是否需要二次measuretotalWeight += lp.weight;// 是否开启权重final boolean useExcessSpace = lp.height == 0 && lp.weight > 0;if (heightMode == MeasureSpec.EXACTLY && useExcessSpace) {/* * 如果LinearLayout的specMode为EXACTLY且子View设置了weight属性,在这里会跳过子View的measure过程,* 同时标记skippedMeasure属性为true,后面会根据该属性决定是否进行第二次measure。* 若LinearLayout的子View设置了weight,会进行两次measure计算,比较耗时。* 这就是为什么LinearLayout的子View需要使用weight属性时候,最好替换成RelativeLayout布局。*/final int totalLength = mTotalLength;mTotalLength = Math.max(totalLength, totalLength + lp.topMargin + lp.bottomMargin);skippedMeasure = true;} else {// ...省略代码...// 已用的高度值final int usedHeight = totalWeight == 0 ? mTotalLength : 0;// 遍历当前child内的子View,并进行测量measureChildBeforeLayout(child, i, widthMeasureSpec, 0, heightMeasureSpec, usedHeight);// 获取当前子View的高度final int childHeight = child.getMeasuredHeight();// ...省略代码...// mTotalLength用于存储LinearLayout在竖直方向的高度final int totalLength = mTotalLength;// 将子View测量的高度添加到mTotalLengthmTotalLength = Math.max(totalLength, totalLength + childHeight + lp.topMargin +lp.bottomMargin + getNextLocationOffset(child));// ...省略代码...}// ...省略代码...// 获取当前ViewGroup控件中,宽度最大值(子控件左右外边距 + 子控件宽度)final int margin = lp.leftMargin + lp.rightMargin;final int measuredWidth = child.getMeasuredWidth() + margin;maxWidth = Math.max(maxWidth, measuredWidth);childState = combineMeasuredStates(childState, child.getMeasuredState());// ...省略代码...}// ...省略代码...// Add in our padding// 高度值还包括LinearLayout自身的上下内边距mTotalLength += mPaddingTop + mPaddingBottom;int heightSize = mTotalLength;// Check against our minimum height// 从最小高度值和当前LinearLayout高度值中取较大值。heightSize = Math.max(heightSize, getSuggestedMinimumHeight());// Reconcile our calculated size with the heightMeasureSpecint heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);// ...省略代码-权重导致的重新测量...if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {maxWidth = alternativeMaxWidth;}// 宽度值还包括LinearLayout自身的左右内边距maxWidth += mPaddingLeft + mPaddingRight;// Check against our minimum widthmaxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());// 将计算子View后得到的宽高值赋值给当前的LinearLayoutsetMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightSizeAndState);// ...省略代码...
}

到此,View的绘制流程就分析完了。

View系列 (三) — Measure 流程详解相关推荐

  1. View的绘制-draw流程详解

    目录 作用 根据 measure 测量出的宽高,layout 布局的位置,渲染整个 View 树,将界面呈现出来. 具体分析 以下源码基于版本27 DecorView 的draw 流程 在<Vi ...

  2. View的绘制-layout流程详解

    目录 作用 根据 measure 测量出来的宽高,确定所有 View 的位置. 具体分析 View 本身的位置是通过它的四个点来控制的: 以下涉及到源码的部分都是版本27的,为方便理解观看,代码有所删 ...

  3. Maven精选系列--三种仓库详解

    转载自 Maven精选系列--三种仓库详解 仓库分类 1.本地仓库 本地仓库就是开发者本地已经下载下来的或者自己打包所有jar包的依赖仓库,本地仓库路径配置在maven对应的conf/settings ...

  4. elasticsearch系列三:索引详解(分词器、文档管理、路由详解(集群))

    目录 一.分词器​ 1. 认识分词器 1.1 Analyzer 分析器 1.2 如何测试分词器 2. 内建的字符过滤器(character filter) 2.1 HTML过滤字符过滤器(HTML S ...

  5. 转载爱哥自定义View系列--Paint详解

    上图是paint中的各种set方法 这些属性大多我们都可以见名知意,很好理解,即便如此,哥还是带大家过一遍逐个剖析其用法,其中会不定穿插各种绘图类比如Canvas.Xfermode.ColorFilt ...

  6. 人脸识别系列三 | MTCNN算法详解上篇

    前言 我们前面分享了PCA,Fisher Face,LBPH三种传统的人脸识别算法,Dlib人脸检测算法.今天我们开始分享一下MTCNN算法,这个算法可以将人脸检测和特征点检测结合起来,并且MTCNN ...

  7. Spring Boot笔记—多线程系列(三)—配置参数详解

    前言 前两篇文章,我们已经学会了如何使用spring boot的多线程和自定义线程池.这篇文章,我们要深入了解上一篇文章中线程池的配置具体含义. 准备工作 说明 为了方便观察线程的情况(如执行完毕数量 ...

  8. Linux Rootkit 系列三:实例详解 Rootkit 必备的基本功能

    本文所需的完整代码位于笔者的代码仓库:https://github.com/NoviceLive/research-rootkit. 测试建议: 不要在物理机测试!不要在物理机测试! 不要在物理机测试 ...

  9. linux rootkit 端口复用,Linux Rootkit系列三:实例详解 Rootkit 必备的基本功能

    前言鉴于笔者知识能力上的不足,如有疏忽,欢迎纠正. 测试建议: 不要在物理机测试!不要在物理机测试! 不要在物理机测试! 概要 在 上一篇文章中笔者详细地阐述了基于直接修改系统调用表 (即 sys_c ...

最新文章

  1. [恢]hdu 2015
  2. 用python画图代码简单-【Matplotlib】利用Python进行绘图
  3. Float浮点数的使用和条件
  4. Python模块 - os
  5. python 返回空格_Python面试之 is 和 == 的区别
  6. linux 文件理解,linux文件系统理解
  7. HTML学习笔记:iframe框架演示
  8. java jdbc连接oracle数据库连接 不抛出异常,JDBC连接Oracle发生异常的原因
  9. 地壳中元素含量排名记忆口诀_高中化学短周期元素推断题的常见题眼
  10. 线性回归的简洁实现(pytorch框架)
  11. Hibernate保存对象出现 org.hibernate.NonUniqueObjectExce
  12. 英特尔傲腾内存linux,英特尔傲腾技术挺简单 三分钟了解这项黑科技
  13. LA 4490 Help Bubu (状压DP)
  14. 嘻哈帝国第一季/全集Empire迅雷下载
  15. 服务重启后 云硬盘,无法使用
  16. Autofill chrome 表格自动填充
  17. python求和1到100_python等差数列求和公式前 100 项的和实例
  18. Zabbix-Linux-邮箱报警
  19. EMC封装成形常见缺陷
  20. 这届网友实在是太有才了!用python爬取15万条《我是余欢水》弹幕

热门文章

  1. 【AI人工智能】AI会对你的行业产生什么影响?
  2. django - html模板
  3. Python--getattr、__getattr__、__getattribute__,倔强一下
  4. DASAN(V5824G)大山 OLT HGU (4GE+2VOIP)配置指导
  5. 黑桃k游戏java实战_#Java小案例 扑克牌小游戏
  6. 分布式应用解决方案之一致性Hash
  7. PPPoE与802.1X在校园网中的应用分析
  8. 算法基础之二叉树理论
  9. phpmailer SMTP ERROR: Password command failed: 526 Authentication failure[0] 阿里云企业邮箱
  10. iOS开发-点击屏幕,键盘消失的极佳方法。