View的Measure过程解析

本文深入探讨Android中View的Measure过程,从DecorView的测量开始,解析MeasureSpec的含义及计算方式,详细阐述View如何根据父容器的限定条件确定自身大小,并通过源码分析展示测量流程的关键步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景

在《Activity.setContentView()内部实现解析》分析了View是如何被添加到DecorView中,但添加到DecorView后,View对用户来说还是不可见的。View在呈现给用户前,它需要经过Measure -> Layout -> Draw三个步骤。本文将从DecorView的源码开始,分析View的Measure过程。

预备知识–测量概述

在分析测量过程前需要确定测量的意义和我对测量过程的定义。View在呈现出来前,需要确定的第一个问题是:显示的View多大?即View的大小,而Measure就是为了确定View的大小。那View的大小是由父容器(提示:父容器是指包含该View的父布局)来决定还是由View自己决定呢?
现在我把Measure过程分为两个阶段
- 第一阶段:父容器计算出measureSpec值,这个值就是父容器对子View的显示区域的限定条件,并传递给子View
- 第二阶段:子View根据显示区域限定条件measureSpec来设置自身大小,当然View也可根据自己的需要设置大小

提示:measureSpec是从父容器传递给子View的,我们可以把measureSpec定义为父容器父子View显示范围的限定条件。
父容器有权利限定子View的显示范围,即预留多少空间给子View。而父容器对子View的限定条件就承载在measureSpec值中。所以在分析过程中说到“显示范围限定条件”指的就是measureSpec值

通过下面的图来说明我对View测量过程的划分
这里写图片描述
这里写图片描述

可得出下面的结论:
父容器传递给子View的measureSpec只是规定了子View的可显示区域,而View的实际显示大小还是得由View来自己设定,View可以根据measureSpec来设置大小,也可随意设置,当然如果子View设定的大小超出父容器规定的范围,那就可以,只是父容器给你预留的空间无法显示完全子View。

预备知识–MeasureSpec

在测量概述中说到父容器会给子View传递显示范围限定条件measureSpec。
现在来分析一下measureSpec是如何承载显示范围限定条件的。
measureSpec是int类型变量,在java中int类型由4个字节组成,每个字节8位,所以measureSpec一共有32位,这32位由两部份组成:specMode和specSize,前面的两位是specMode标志位, 后面30位是specSize,如下图所示:

MeasureSpec的组成

系统提供MeasureSpec工具类,使得我们可以很方便的从measureSpec中取出specSize和specMode

int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

// MeasureSpce的内部实现
int  specMode = widthMeasureSpce & 0xB000;  // 0xB000的二进制为1100 0000 0000 0000
int  specSize = widthMeasureSpec & 0x3fff;  // 0x3fff的二进制为0011 1111 1111 1111

specMode有三种模式,不同的模式对specSize有不同的解释
这里列出specMode的每个模式,以及每种模式对specSize的解释

specMode specSize
MeasureSpec.EXACTLY 父容器告诉子View:你可以使用SpecSize作为的你的长宽值
MeasureSpec.AT_MOST 父容器告诉子View:你可根据需求设置你的大小,但你设定的大小不要超过SpecSize这个值
MeasureSpec.UNSPECIFIED 父容器告诉子View:你可根据需求设置你的大小,我不会限制你的可显示范围

对于MeasureSpec这个值是如何计算出来的,我们在下面会详细的分析,你只要知道,父容器会给子View传递显示范围限制条件measureSpec,而这个显示范围限制条件是以一个int类型的变量measureSpec

分析

《Activity.setContentView()内部实现解析》中说到了在ViewRootImpl.setView()方法中,会调用WindowSession.addToDisplay( )方法通知WindowManagerService添加一个Window,WindowManagerService进行相应的逻辑处理后,会通知ViewRootImpl对象对DecorView进行布局,而布局的入口函数是performTraversals( ),该方法代码量很长,但我们只需关注对DecorView开始测量的入口。
提示:由于DecorView是最顶级的View,所以DecorView不存在真正的父容器,在后面的分析中我们说到的DecorView的父容器指的就是ViewRootImpl这个类,虽然这个类不属于View,但由于ViewRootImpl是直接管理DecorView的,所以就把ViewRootImpl当做DecorView的父容器

private void performTraversals() {

    省略....

    // frame是Rect类对象,这里我们只需知道frame是系统给定的用于显示DecorView的区域大小
    mWidth = frame.width();
    mHeight = frame.height();

    // lp是前面为DecorView创建的布局参数,这里lp.with和lp.height都是MATCH_PARENT
    // 通过getRootMeaureSpec()方法确定DecorView的childWidthMeasureSpec和childHeightMeasureSpec
    // 即DecorView的显示范围限定条件
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值