MeasureSpec 与 LayoutParams 不得不说的二三事

本文深入探讨了Android中的MeasureSpec和LayoutParams在视图测量中的作用。MeasureSpec封装了父视图传递给子视图的布局要求,而LayoutParams则表达了子视图自身的布局期望。文章详细解析了MeasureSpec的模式(UNSPECIFIED、EXACTLY和AT_MOST)及其在视图测量过程中的应用,同时阐述了LayoutParams在不同布局中的具体实现。通过对MeasureSpec和LayoutParams的理解,开发者可以更好地掌握Android的测量体系,解决视图布局中的问题。

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


已开通新的博客,后续文字都会发到新博客

https://0xforee.top/2019/07/01/android-measurespec-layoutparams/


MesureSpec,测量规格

前言

MeasureSpec 这个话题往大了说,关系到整个 Android 测量体系的设定,往小了说,只是一个封装了位运算的内部静态类而已。当然我们不会仅仅满足于后者,所以我们就借 MeasureSpec 来简单窥探一下 Android 的测量体系。

正文

MeasureSpec,Android 给出的解释是封装了从父视图传递给子视图的布局要求。

A MeasureSpec encapsulates the layout requirements passed from parent to child.

这句话其实已经讲得足够清晰了,但对于从未接触过 MeasureSpec 的初学者来说,还是有点太过简单。

在 Android 的视图系统中,屏幕资源是被整个 View 树一层层消费的,这个 View 树中,往上是父 View,往下是子 View,子 View 的资源都是直接来自父 View,所以在测量的过程中,子 View 并不能不受限制的获取屏幕资源,也要受限于父 View 所给的布局要求,这个要求就是通过 MeasureSpec 这个类来传递的。

先来讲一讲 LayoutParams

为什么在这里会讲到 LayoutParams,因为要确定子 View 自身的测量状态,除了知道父 View 的限制之外,还需要知道子 View 自身对尺寸的期望,而 View 自身的期望在代码中就是通过 LayoutParams 来封装的。

所以,可以这么说, LayoutParams 就是影响测量过程另一大因素。

LayoutParams are used by views to tell their parents how they want to be laid out.

LayoutParams 是 ViewGroup 的一个内部类,被用于来告诉父视图,子视图想怎么样被布局。另外,每个父视图都有继承 ViewGroup.LayoutParams 自己的内部实现,所以对于不同的布局,我们可使用的 LayoutParams 参数也不一样,这个我们后边会详细说明

我们知道,一般的创建一个 View 有两种方式,一种是通过 Java 的方式 new 一个 View 对象,另一种就是通过 Android 的方式 Inflate 一个 View 对象。这两种方式的差别之处在于,new 一个对象是需要手动设置大量的参数,而 Inflate 一个对象,只需要在 xml 布局文件中写好就OK,系统帮我们做了大量的准备工作而已,效率高了不少。除此之外,其实是没有区别的。

那么在写 xml 布局的时候,会发现每个 View 有两个参数是必不可少的,layout_width 和 layout_height ,对应于 View 的宽和高,这两个 xml 属性,其实对应的就是 LayoutParams 中的 width 和 height

相对应的:

  • margin 相关的参数,可以在 android.view.ViewGroup.MarginLayoutParams 中找到
  • RelativeLayout 相关的参数,可以在 android.widget.RelativeLayout.LayoutParams 中找到
  • weight 相关的参数,可以在 android.widget.LinearLayout.LayoutParams 中找到

这里附上一个 Android 中 LayoutParams 的类图,感兴趣的可以详细查询

[外链图片转存失败(img-ejUjUW55-1562082904351)(./LayoutParams_Class.jpg “LayoutParams_Class.jpg”)]

以上这些内容理解之后就可以解释很多初学者的一些疑惑:

  1. 我们将获取到的 LayoutParams 強转为 ViewGroup.LayoutParams 会导致 margin 丢失,是因为 margin 是 ViewGroup.MarginLayoutParams 才有的属性
  2. RelativeLayout 不支持 layout_gravity 的属性,LinearLayout 不支持 alignInParent 的属性,是因为他们互相不关联,相同之处在于只是都是继承自 MarginLayoutParams
  3. 我们获取的控件的 LayoutParams 是父视图的 LayoutParams 的内部实现类,而不是子视图自己的,因为设置给子视图的 LayoutParams 是被父视图拿来测量用的,而不是自己使用

注意:因为 padding 是占用 View 内部空间的属性,会干预自身空间内 content 中的显示,并不会影响子 View 在父 view 中所占用的尺寸,所以并没有放在 LayoutParmas 中,而是在给子 View 分配空间,或者绘制自己的 content 的时候才会用到,在后边解析 FrameLayout 的时候也会提到这一点

MeasureSpec 类的解析

理解了 LayoutParams 之后,接下来看一下 MeasureSpec 类的具体内容,因为对于后边涉及到的 MeasureSpec 的转换,这里的内容是必不可少的。

按照惯例,我们先来看类的注释

A MeasureSpec encapsulates the layout requirements passed from parent to child.

/** 
 * 
 * Each MeasureSpec represents a requirement for either the width or the height. 
 * A MeasureSpec is comprised of a size and a mode. 
 * 
 * MeasureSpecs are implemented as ints to reduce object allocation. This class 
 * is provided to pack and unpack the <size, mode> tuple into the int. 
 */ 

A MeasureSpec is comprised of a size and a mode.

MeasureSpec 是通过 int 值的位运算来实现的,目的是减少对象的内存分配。将32位 int 值的前两位作为 mode(将 int 值左位运算30位),将后30位作为 size,如下

private static final int MODE_SHIFT = 30; 
private static final int MODE_MASK  = 0x3 << MODE_SHIFT; 
public static final int UNSPECIFIED = 0 << MODE_SHIFT; 
public static final int EXACTLY     = 1 << MODE_SHIFT; 
public static final int AT_MOST     = 2 << MODE_SHIFT; 

MeasureSpec 中还带了一些方法,提供 mode 和 size 的打包和解包,方便 MeasureSpec 到 size 和 mode 的互相转换,这里我们不再细说,可以参看 MeasureSpec 的具体内容。

MeasureSpec 模式

每个 MeasureSpec 代表一个宽或者高的要求。一个 MeasureSpec 由尺寸 size 和模式 mode 组成。
其中 size 表示当前 View 已经确定的尺寸,模式表示要对子 View 限定的模式。

可以这么说,MeasureSpec 主要是对子 View 起作用,如果当前 View 已经是最后一层 View,那么 最后确定的 MeasureSpec 是没有多大意义的

接下来,简单讲一下 MeasureSpec 表示的几种模式

/** There are three possible modes: 
 *
 * UNSPECIFIED 
 * The parent has not imposed any constraint on the child. It can be whatever size 
 * it wants.  
 * 
 * EXACTLY 
 * The parent has determined an exact size for the child. The child is going to be 
 * given those bounds regardless of how big it wants to be. 
 * 
 * AT_MOST
 * The child can be as large as it wants up to the specified size. 
 * 
 * /

我们来看一下有哪些模式

  • UNSPECIFIED

父 View 没有施加任何限制给子 View,可以是子 View 想要的任何尺寸

  • EXACTLY

父 View 已经限定了子 View 的精确尺寸,子 View 必须是这个尺寸,无论他自己想要多大的尺寸

  • AT_MOST

子 View 可以是不超过某个特定的值任意大小

直接看到这些模式可能有点懵,各种要求和被要求,没有关系,我们先对这些模式有个大概的认识,在后续转换的过程中,我们再回来参考就可以

注意:Android 的类的注释往往言简意赅,理解一个类之前,读注释会带来很大的帮助,要养成读类注释的习惯很多人遇到一个函数不理解时,下意识的跑去 Google, Baidu,但其实对于 Android 来说,函数的使用说明都已经放到了源码注释中,直接读注释会比搜索工具来的更快,也更准确。

MeasureSpec 的使用解析

能找到这篇文文章并看到这里的童鞋,一定是已经了解过 Android 的整个绘制流程,这里我们简单的提及就可。没有看过的同学,可以通过之前的 invalidate 系列文章 来了解这部分内容

MeasureSpec 的具体含义,我们在前边已经讲过了,接下来进行的 MeasureSpec 的解析,我们需要弄明白两个问题:

  1. MeasureSpec 是起始自哪里?
  2. MeasureSpec 是怎么从父 View 传递给子 View 的,这其中做了哪些处理?

我们先来看第一个问题

MeasureSpec 的起始通过前面的说明我们已经知道,MeasureSpec 是辅助父 View 测量子 View 的一个工具,所以测量过程中子 View 的 MeasureSpec 都是来自于父 View ,父 View 的 MeasureSpec 又来自于父 View 的父 View,那么最开始的 MeasureSpec 是来自哪里的呢,我们接下来就跟着 View 测量的起源来分析一下最开始的 MeasureSpec

# android.view.ViewRootImpl#performTraversals() 
private void performTraversals() {
   
    
        
    .......
	
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); 
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); 
    // Ask host how big it wants to be 
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); 
    
	....... 
} 

performTraversals 是每一次屏幕进行测量布局绘制的入口,performMeasure 是真正测量的开始,而在performMeasure 之前的 getRootMeasureSpec 显然就是 MeasureSpec 的起始

# android.view.ViewRootImpl#getRootMeasureSpec() 
/** 
 * Figures out the measure spec for the root view in a window based on it's 
 * layout params. 
 * 
 * @param windowSize 
 *            The available width or height of the window 
 * 
 * @param rootDimension 
 *            The layout params for one dimension (width or height) of the 
 *            window. 
 * 
 * @return The measure spec to use to measure the root view. 
 */ 
private static int getRootMeasureSpec(int<
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值