游乐游手机版
首页/网络安全/文章详情

Android中onMeasure详解:深入理解布局测量机制

时间:2026-08-20 21:17
今天继续学习 Android 自定义组件,参考文档:docs guide topics ui custom-components html其中有两个对界面布局影响很大的方法:onDraw() 和 onMeasure()。onDraw() 相对比较容易理解,而 onMeasure() 往往更难一些,也

今天继续学习 Android 自定义组件,参考文档:docs/guide/topics/ui/custom-components.html

android中onMeasure初看,深入理解布局之一!

其中有两个对界面布局影响很大的方法:onDraw() 和 onMeasure()。

onDraw() 相对比较容易理解,而 onMeasure() 往往更难一些,也更复杂。引用文档中的原话就是:

onMeasure() is a little more involved. 其实还有一个原因,就是我当时对 measure 这个单词理解得不够准确,后来查了词典才放心,确实就是“测量”的意思。

要实现 onMeasure() 方法,基本需要处理好以下三个方面(最终结果就是:通过代码计算出测量值,再调用 View 的相关方法进行设置,从而告诉为你的 View 安排位置和大小的父容器,你到底需要多大的显示空间):

1. 传递进来的参数 widthMeasureSpec 和 heightMeasureSpec,是你最终测量结果必须参考的限制条件。

The overidden onMeasure() method is called with width and height measure specifications(widthMeasureSpec and heightMeasureSpec parameters,both are integer codes representing dimensions) which should be treated as requirements for the restrictions on the width and height measurements you should produce.
2. 你在 onMeasure 中计算并设置的 width 和 height,会直接用于组件渲染,因此应尽量保持在传递进来的宽高约束范围之内。

虽然你也可以选择让设置的尺寸超过传递进来的限制,但这样一来父容器就有多种处理方式,比如 clipping(剪裁)、scrolling(滚动)、抛出异常,或者再次调用 onMeasure() 方法(也许会传入新的声明参数)。

Your component's onMeasure() method should calculate a measurement width and height which will be required to render the component.it should try to stay within the specified passed in.although it can choose to exceed them(in this case,the parent can choose what to do,including clipping,scrolling,throwing an excption,or asking the onMeasure to try again,perhaps with different measurement specifications).
3. 一旦 width 和 height 计算完成,就必须调用 View.setMeasuredDimension(int width,int height) 方法,否则会导致抛出异常。 Once the width and height are calculated,the setMeasureDimension(int width,int height) method must be called with the calculated measurements.Failure to do this will result in an exceptiion being thrown

在 Android 提供的一个自定义 View 示例中(API Demos 里的 view/LabelView),可以看到一个重写 onMeasure() 方法的

实例,整体上也比较容易理解。

    /**
* @see android.view.View#measure(int, int)
*/
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
setMeasuredDimension(measureWidth(widthMeasureSpec),
measureHeight(heightMeasureSpec));
}

/**
* Determines the width of this view
* @param measureSpec A measureSpec packed into an int
* @return The width of the view, honoring constraints from measureSpec
*/
private int measureWidth(int measureSpec) {
int result = 0;
int specMode = MeasureSpec.getMode(measureSpec);
int specSize = MeasureSpec.getSize(measureSpec);

if (specMode == MeasureSpec.EXACTLY) {
// We were told how big to be
result = specSize;
} else {
// Measure the text
result = (int) mTextPaint.measureText(mText) + getPaddingLeft()
+ getPaddingRight();
if (specMode == MeasureSpec.AT_MOST) {
// Respect AT_MOST value if that was what is called for by measureSpec
result = Math.min(result, specSize);
}
}

return result;
}

下面直接看 measureWidth()。

首先看到的是参数,它代表宽度或高度对应的 MeasureSpec。

在 Android 2.2 文档中,对 MeasureSpec 的说明是:

一个 MeasureSpec 封装了从父容器传递给子容器的布局要求。

每一个 MeasureSpec 都表示宽度或者高度方面的测量说明。

一个 MeasureSpec 本质上是“大小 size + 模式 mode”的组合值,一共包含三种模式。

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 compsized of a size and a mode.There are three possible modes:
(1)UPSPECIFIED :父容器对子容器没有任何限制,子容器想要多大就可以多大。 UNSPECIFIED The parent has not imposed any constraint on the child.It can be whatever size it wants
(2) EXACTLY

父容器已经为子容器确定了尺寸,子容器应当遵守这些边界,不管它自己希望占用多大的空间。

EXACTLY The parent has determined and exact size for the child.The child is going to be given those bounds regardless of how big it wants to be.
(3) AT_MOST

子容器可以在声明大小之内,取任意不超过该限制的尺寸。

AT_MOST The child can be as large as it wants up to the specified size
MeasureSpec 是 View 类下的一个静态公开类。MeasureSpec 之所以使用一个 int 值来表示,是为了减少对象分配带来的开销。此类用于

把 size 和 mode 打包,或者从一个 int 中解包出来。

MeasureSpecs are implemented as ints to reduce object allocation.This class is provided to pack and unpack the size,mode tuple into the int
我当时比较好奇的是:到底如何把两个值打包到一个 int 中,又是怎样再把它们解包出来的。

MeasureSpec 类的代码如下(注释已经被我删除,因为上面已经说明过了):

    public static class MeasureSpec {
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;

public static int makeMeasureSpec(int size, int mode) {
return size + mode;
}
public static int getMode(int measureSpec) {
return (measureSpec & MODE_MASK);
}
public static int getSize(int measureSpec) {
return (measureSpec & ~MODE_MASK);
} }

我还特地把它们的十进制值打印了出来:

mode_shift=30,mode_mask=-1073741824,UNSPECIFIED=0,EXACTLY=1073741824,AT_MOST=-2147483648

接着又觉得有必要把它们的二进制值也打印出来,如下:

mode_shift=11110, // 30

mode_mask=11000000000000000000000000000000,

UNSPECIFIED=0,

EXACTLY=1000000000000000000000000000000,

AT_MOST=10000000000000000000000000000000

MODE_MASK  = 0x3 << MODE_SHIFT //也就是说MODE_MASK是由11左移30位得到的.因为Java用补码表示数值,最后得到的值最高位是1,所以它就是负数了
对于上面的数值,我们可以这样理解:不要把 0x3 单纯看成十进制的 3,而要把它看成二进制的 11,

而把 MODE_SHIFT 看成 30。那么为什么这里是二进制的 11 呢?

因为这里只有三种模式,所以只需要两位二进制就可以表示;如果有四种模式,那么也仍然需要两位,而不是三位,这里重点是两位已经能覆盖足够的组合。

我们这样来看:

UNSPECIFIED=00000000000000000000000000000000,

EXACTLY=01000000000000000000000000000000,

AT_MOST=10000000000000000000000000000000

也就是说,0、1、2

分别对应 00、01、10

当和 11 做按位与运算时,00 &11 仍然得到 00,11&01 得到 01,10&

写到这里,相信看到这里的朋友,对 Android 布局测量和 onMeasure 的基本思路也已经能理解了。

return (measureSpec & ~MODE_MASK); 应该理解为 return (measureSpec & (~MODE_MASK));

来源:https://apiv1.oschina.net/oschinapi/blog/detail?id=51247
上一篇Debian Backlog是否与安全漏洞风险相关 下一篇Debian中如何使用SecureCRT实现加密传输
本站内容用于信息整理与展示,如有侵权或内容问题请及时联系处理。

相关推荐

补充同频道和同主题内容,方便继续浏览更多相关内容。

同类最新

继续查看同栏目最近更新的文章。

更多
DDoS攻击的三大主要形式:原理、特征与防御重点
网络安全 · 2026-08-31

DDoS攻击的三大主要形式:原理、特征与防御重点

DDoS攻击主要分为基于流量(Volume)、基于应用层(Application)和基于协议(Protocol)三种形式。流量型攻击通过海量数据淹没带宽;应用层攻击利用Web漏洞耗尽服务器资源;协议层攻击则利用TCP握手缺陷导致系统挂起。了解这些原理是制定针对性防御策略的基础。

如何有效预防和缓解DDoS攻击:5大核心策略详解
网络安全 · 2026-08-31

如何有效预防和缓解DDoS攻击:5大核心策略详解

面对DDoS攻击,单纯增加带宽已非长久之计。本文详解5大核心防护策略:优化网络硬件配置、建立DNS冗余机制、部署透明缓解技术、引入负载平衡器及专用Anti-DDoS模块。通过合理组合这些技术手段,可有效抵御SYN泛洪、Slowloris等常见攻击,保障业务连续性与网站可用性。

DDoS防护四大误区:CDN、防火墙与黑名单的局限性解析
网络安全 · 2026-08-31

DDoS防护四大误区:CDN、防火墙与黑名单的局限性解析

许多企业误以为CDN、防火墙或黑名单能完全抵御DDoS攻击。本文深入解析四大常见误区:CDN仅提供部分缓解、静态黑名单易失效、防火墙算力有限且可能成为目标、阈值警报仅具滞后性。了解这些局限性,有助于构建更立体的防御体系,避免在攻击发生时措手不及。

常见DDoS攻击类型详解:原理、特征与防御策略
网络安全 · 2026-08-31

常见DDoS攻击类型详解:原理、特征与防御策略

本文详细解析四种常见DDoS攻击类型:SYN Flood利用TCP三次握手缺陷耗尽资源;UDP Flood通过海量数据包造成带宽拥塞;ICMP Flood利用Ping请求消耗系统算力;应用层Flood针对Web脚本进行高频请求。了解其原理是制定有效防御策略的基础。

如何有效抵御DDOS攻击:4种核心防护方案解析
网络安全 · 2026-08-31

如何有效抵御DDOS攻击:4种核心防护方案解析

面对DDOS攻击,企业需构建多层防护体系。本文详解四大核心策略:利用反向路由器查询进行流量清洗,通过GCDN智能分配节点隐藏源站IP,部署负载均衡硬件分担压力,以及接入高防机房抵御数百G恶意流量。掌握这些技术,可最大程度保障业务连续性。