View系列 (三) — Measure 流程详解

2023-10-26

一、概述

测量过程分为 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()默认返回-1
    return intrinsicWidth > 0 ? intrinsicWidth : 0;
}

View.getDefaultSize()

// View.class
public static int getDefaultSize(int size, int measureSpec) {
	//参数 measureSpec 是子View的MeasureSpec
    int 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内部的所有子View
    for (int i = 0; i < size; ++i) {
        final View child = children[i];
        // 子View显示模式不为GONE
        if ((child.mViewFlags & VISIBILITY_MASK) != GONE) {
        	// 测量子View
            measureChild(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属性设置,用于后面判断是否需要二次measure
        totalWeight += 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测量的高度添加到mTotalLength
            mTotalLength = 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 heightMeasureSpec
    int heightSizeAndState = resolveSizeAndState(heightSize, heightMeasureSpec, 0);
    
    // ...省略代码-权重导致的重新测量...
    
    if (!allFillParent && widthMode != MeasureSpec.EXACTLY) {
        maxWidth = alternativeMaxWidth;
    }
	// 宽度值还包括LinearLayout自身的左右内边距
    maxWidth += mPaddingLeft + mPaddingRight;

    // Check against our minimum width
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

	// 将计算子View后得到的宽高值赋值给当前的LinearLayout
    setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState), heightSizeAndState);

	// ...省略代码...
}

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

View系列 (三) — Measure 流程详解 的相关文章

  • View系列 (三) — Measure 流程详解

    Measure 流程详解 一 概述 二 单一 View 的测量流程 1 流程图 2 源码分析 三 ViewGroup 的测量流程 1 流程图 2 源码分析 一 概述 测量过程分为 View的measure过程 和 ViewGroup的mea
  • android之媒体硬解OMX的实现

    转自 http blog csdn net vincent blog article details 7578112 android的多媒体部分采用的编解码标准是OMX 当然这个标准是用于硬件编解码的 软件编解码在这里我就不说了 直接从st
  • android 启动过程分析

    Servicemanager需要先启动 zygote后面的service需要用到servicemanager的服务
  • 《Linux设备节点创建》用户空间ueventd创建设备节点规则

    转自 http blog csdn net tankai19880619 article details 11726371 说明 本文基于Android2 3和Linux2 6 其余版本仅供参考 一 devfs udev和sysfs是什么关
  • Activity启动流程源码分析-浅析生命周期函数

    源码分析 接着上一篇 Activity启动流程源码分析 setContentView源码阅读 的讲解 本节介绍一下Activity的生命周期函数何时被调用 要看Activity的生命周期函数何时被调用 不得不翻阅 ActivityThrea
  • android 系统级应用和服务的启动流程

    activityManagerService java 1 systemRaady 收到systemReady 通知 2 AppGlobals getPackageManager getPersistentApplications STOC
  • http://blog.csdn.net/haomcu/article/details/7267090

    转自 http blog csdn net haomcu article details 7267090 一 Android平台Wifi的基本代码路径 1 Wpa supplicant源码部分 external wpa supplicant
  • 为什么ViewGroup的onDraw()方法不执行

    问题 ViewGroup onDraw不执行的原因 怎么让ViewGroup onDraw执行 android代码一直在优化 我看了几个版本的源码 目前 我用的是API30的源码 再去看ViewGroup为什么不走onDraw 的时候 已经
  • 1-APP启动源码分析-1

    桌面app也就是我们认识的launcher app 点击app icon启动到app内部的过程分为2种情况 一种是冷启动 一种叫热启动 冷启动 系统没有创建过app的进程 也就是后台没有此app进程 所以冷启动系统会创建一个新的进程分配给a
  • Python+uiautomator2手机UI自动化测试实战 --1. 环境搭建

    转自 https blog csdn net ricky yangrui article details 81414870 一 简介 uiautomator2是一个python库 用于Android的UI自动化测试 其底层基于Google
  • Android apk安装管理(PackageManagerService 分析)

    Android apk安装管理 PackageManagerService 分析 本篇主要分析了系统启动阶段包管理服务的启动流程 其中的几个接口在apk安装时也会被调用 包管理服务启动时主要做的工作大致有如下几方面 1 建立java层的in
  • Activity启动流程

    简述 Activity 启动分为两种 1 Activity中通过startActivity 方法启动一个Activity 2 从桌面通过点击应用图标启动一个App然后显示Activity 我们通过第二点来分析 更全面一点 先走一波流程图 以
  • Android Display System --- Surface Flinger

    转自一醉千年大大 http blog csdn net yili xie archive 2009 11 12 4803527 aspx SurfaceFlinger 是Android multimedia 的一个部分 在Android 的
  • Android源码分析 - Service启动流程

    开篇 本篇以android 11 0 0 r25作为基础解析 在之前的文章中 我们已经分析过了四大组件中Activity和ContentProvider的启动流程 这次我们就来讲讲四大组件之一的Service是如何启动和绑定的 流程图 在查
  • MediaScanner生成及保存thumbnail的方式

    转自 http blog csdn net qikaibinglan article details 6130589 本文简单研究一下MediaScanner生成及保存thumbnail的方式 并给出代码快速查询图片的thumbnail 1
  • android MediaPlayer 中的JNI总结

    1 在android media MediaPlayer cpp 中 定义fields静态变量 里面有两个重要的成员变量 context 用来保存创建的mediaplayer post event 用来将JNI层的事件回调给JAVA层 实现
  • android input 机制源码分析

    具体文字说明请参考 http blog csdn net luoshengyang article details 6882903
  • Dalvik虚拟机简要介绍和学习计划

    通过修改 android framework base core jni AndroidRuntime cpp 中的 property get dalvik vm heapsize heapsizeOptsBuf 4 16m 来修改 dal
  • android 中的的 sp/wp/RefBase

    转自 http blog csdn net innost article details 6752443 5 1 概述 初次接触Android源码时 见到最多的一定是sp和wp 即使你只是沉迷于Java世界的编码 那么Looper和Hand
  • android recovery 系统代码分析【精】

    转自 http blog csdn net andyhuabing article details 9226569 http blog csdn net andyhuabing article details 9248713 最近做Reco

随机推荐

  • 可用的公开 RTSP/ RTMP 在线视频流资源地址(亲测可行)

    可用的公开 RTSP RTMP 在线视频流资源地址 亲测可行 时间节点 2023 01 23 rtsp rtsp wowzaec2demo streamlock net vod mp4 BigBuckBunny 115k mp4 rtmp
  • R语言独立性检验-基础

    一 介绍三种检验独立性方法 1 卡方独立性检验 用chisq test 函数对二维表的行变量和列变量进行卡方独立性检验 gt library vcd gt mytable lt xtabs Treatment Improved data A
  • 基于Python的招聘系统的设计与实现-应聘兼职Python爬虫安装数据分析与可视化计算机毕业设计

    更多项目资源 最下方联系我们 目录 一 项目技术介绍 二 项目配套文档 部分内容 资料获取 一 项目技术介绍 该项目含有源码 文档 PPT 配套开发软件 软件安装教程 项目发布教程 包运行成功以及课程答疑与微信售后交流群 送查重系统不限次数
  • 打印机"启用双向支持"的意思

    在打印机的属性选项里面 有一项 启用双向支持 的选项 但是具体有什么作用 一直都不明白 今天特意查了一些资料 启用双向支持 简单来说就是来回打印 打印头从左向右走动时能打印 从右向左回来时不能打印 如果不启用 仅仅是从左向右走动时打印 重庆
  • pe联想服务器装系统教程视频,演示联想电脑u盘重装系统xp教程

    联想电脑U盘重装XP系统的方法很多朋友询问 其实现在很多电脑已经不支持XP系统的安装了 如果你的联想电脑是近几年购买的 还是安装win10系统比较保险 当然联想电脑安装系统过程中遇到问题也可以联系人工客服 联想电脑如何使用U盘重装系统XP呢
  • 'dependencies.dependency.version' for XXX:jar is missing

    SSM项目报错 pom文件
  • HAL库 串口收发函数解析

    一 UART Receive IT 对于CubeMX生成的代码 USART1 IRQHandler void 函数为了提高中断效率采用了回调机制 业务代码可以等中断关闭了再去处理 这样中断处理不会占用太多时间影响程序的执行效率 HAL库将函
  • chatGPT爆火,什么时候中国能有自己的“ChatGPT“

    目录 引言 一 ChatGPT爆火 二 中国何时能有自己的 ChatGPT 三 为什么openai可以做出chatGPT 四 结论 引言 随着人工智能技术的不断发展 自然语言处理技术也逐渐成为了研究的热点之一 其中 ChatGPT作为一项领
  • Mac利用VirtualBox安装虚拟机网络设置桥接模式不能上网问题记录

    Mac笔记本电脑利用VirtualBox安装了一个Ubuntu20 04虚拟机 默认网络为网络地址转换 NAT NAT模式是最简单的实现虚拟机上网的方式 你可以这样理解 Vhost访问网络的所有数据都是由主机提供的 vhost并不真实存在于
  • 剑指 Offer 61. 扑克牌中的顺子 --思路和心得

    class Solution 思路 用一个参数special来代表相差的值 就比如 1 2 5 2和5相差两个数字 就将 special 2 在进行此步运算的时候要时刻注意让当前数的前一个数不为0 如果遇到一个零 就让special 最后如
  • 【Pytorch】import torch报错from torch._C import *

    好久不用的conda环境torch env下导入torch报错 import torch Traceback most recent call last File line 1 in File F Coding Tools Anaconda
  • 简单易懂的讲解深度学习(入门系列之八)

    神经网络也许是计算机计算的将来 一个了解它的好方法是用一个它可以解决的难题来说明 假设给出 500 个字符的代码段 您知道它们是C C JAVA或Python 现在构造一个程序 来识别编写这段代码的语言 一种解决方案是构造一个能够学习识别这
  • 将windows下编写的QT代码在arm开发板(imx6ull)上运行

    将windows下编写的QT代码在arm开发板上运行 1 下载并编译tslib库 2 下载并编译Qt源码 2 1 下载Qt5的源码 2 2 修改qmake conf 2 3 配置编译选项 2 4 编译 3 开发板上配置Qt环境 3 1 复制
  • 2、基于ARM平台Golang简单Demo:使用sqlite3数据库

    arm平台为M3352核心板 编译环境为Ubuntu14 04 目标 在Ubuntu环境编译Go代码 在arm平台运行 使用sqlite3数据库 源代码文件名 main go package main import database sql
  • 【C】ringbuffer的C语言实现

    最近在学习Android 下的Bluedroid时 看到在Bluedroid中实现了ringbuffer这一数据结构 比较简洁 所以独立出来进行分享 Bluedorid官方源码路径 本文分享的ringbuffer例子源码路径 什么是ring
  • C语言进阶-文件操作

    目录 一 前言 一 FILE结构类型 二 fopen函数 三 fclose函数 四 各类常用文件操作函数 1 fscanf 2 fprintf 3 fread 4 fwrite 5 fgetc 6 fputc 7 fgets 8 fputs
  • 三天还不够,非常新加坡

    三天还不够 非常新加坡 一 9月2日 9月5日我有幸参加了招行CTRIP推出的非常新加坡之旅 4日2晚新加坡自由行 由于是半夜的红眼航班出发 其实满打满算也就三天 本来按计划是跟一个网上结识的MM联合报名 结伴同游的 但非常遗憾的是在出发当
  • 【研究生工作周报】(DAI2I)

    Domain Adaptive Image to image Translation 文章目录 Domain Adaptive Image to image Translation 论文摘要 一 Domain adaptation 样本自适
  • 资源调度器的一些基本问题

    1 调度算法 Capacity based DRF dominant recourse fairness label based等 多态化 插件化 可以多种策略一起工作 对应于不同Job 优先级 job特性 service or batch
  • View系列 (三) — Measure 流程详解

    Measure 流程详解 一 概述 二 单一 View 的测量流程 1 流程图 2 源码分析 三 ViewGroup 的测量流程 1 流程图 2 源码分析 一 概述 测量过程分为 View的measure过程 和 ViewGroup的mea