首页 / 资讯中心 / 文章详情

Android MeasureSpec 详解

Android MeasureSpec 详解 ★ FEATURED ARTICLE
MeasureSpec是父容器向子 View 传递尺寸约束的唯一载体:一个 32 位 int,高 2 位是测量模式,低 30 位是测量尺寸。1. 为什么需要 MeasureSpecAndroid 的 View 是"父管子"的树形结构:子 View 知道自己内容需要多大,父容器知道这块地盘还剩多大。两者信息不对称,就需要一份协商协议——只传一个int size无法区分"硬性要求"和"上限",所以有了MeasureSpec:尺寸 + 意图。整条链路分四步:① 父容器(比如 LinearLayout) │ 用自己的 MeasureSpec + 子的 LayoutParams,算出子该遵守的 spec ▼ ② 子 View.measure(childWidthSpec, childHeightSpec) │ measure() 内部回调 onMeasure(),子在这里算出「我要多宽多高」 ▼ ③ 子 setMeasuredDimension(w, h) │ 把算出的结果分别写进 mMeasuredWidth 和 mMeasuredHeight 两个字段 ▼ ④ 父容器读回这两个值,在 onLayout() 里摆放子的位置(测量阶段至此结束)一句话概括:父出规则 → 子做计算 → 父收结果。规则来自「父的 MeasureSpec + 子的 LayoutParams」,计算发生在onMeasure(),结果被父容器用在onLayout()里。2. 二进制结构:一个 int 装两样东西MeasureSpec本质上是对一个int的封装(Java 里是View的静态内部类,Kotlin 里可以理解为一个value class)。31 30 | 29 .................................. 0 ┌──────┬───────────────────────────────────────┐ │ Mode │ Size │ │ 2 bit│ 30 bit │ └──────┴───────────────────────────────────────┘ ↑ 高 2 位存模式 ↑ 低 30 位存尺寸核心常量:publicstaticclassMeasureSpec{privatestaticfinalintMODE_SHIFT=30;privatestaticfinalintMODE_MASK=0x3MODE_SHIFT;// 0xC0000000publicstaticfinalintUNSPECIFIED=0MODE_SHIFT;// 0x00000000publicstaticfinalintEXACTLY=1MODE_SHIFT;// 0x40000000publicstaticfinalintAT_MOST=2MODE_SHIFT;// 0x80000000}打包与解包:// 打包:把 size 截断到 30 位,和 mode 按位或publicstaticintmakeMeasureSpec(intsize,intmode){// 保证 size 不会污染高 2 位的 modereturn(size~MODE_MASK)|(modeMODE_MASK);}// 解包publicstaticintgetMode(intmeasureSpec){return(measureSpecMODE_MASK);}publicstaticintgetSize(intmeasureSpec){return(measureSpec~MODE_MASK);// 即 0x3FFFFFFF,最大约 1G}手动构造 spec 的常见写法:intspec=View.MeasureSpec.makeMeasureSpec(300,View.MeasureSpec.EXACTLY);view.measure(spec,spec);3. 三种测量模式模式常量父容器的含义子 View 应该怎么做EXACTLY1 30你的尺寸已经定了,就是size直接使用specSize,不要再争辩AT_MOST2 30你最大不能超过size取「内容需要的大小」与specSize的较小值UNSPECIFIED0我不管你,你自己随意完全按内容需要返回(可滚动容器常用)对应到 XML 属性,直觉上:LayoutParams父为 EXACTLY父为 AT_MOST父为 UNSPECIFIEDmatch_parentEXACTLY(父可用空间)AT_MOST(父可用空间)UNSPECIFIED(0 或父可用空间)wrap_contentAT_MOST(父可用空间)AT_MOST(父可用空间)UNSPECIFIED(0 或父可用空间)100dpEXACTLY(100dp)EXACTLY(100dp)EXACTLY(100dp)可以看出:具体数值永远变成 EXACTLY,这是最稳定的一条规则。典型的 UNSPECIFIED 场景:ScrollView测量唯一子 View 的高度(只能滚一屏,所以高度不设上限)。ListView/RecyclerView测量 item 的高度。手动调用view.measure(0, 0)或makeMeasureSpec(0, UNSPECIFIED)做预测量。PopupWindow在WRAP_CONTENT下测量内容。4. 核心 API 速查// ---- 打包 / 解包 ----View.MeasureSpec.makeMeasureSpec(intsize,intmode)// 构造一个 measuicSpecView.MeasureSpec.getMode(intmeasureSpec)// 取模式View.MeasureSpec.getSize(intmeasureSpec)// 取尺寸View.MeasureSpec.toString(intmeasureSpec)// 调试打印,如 "EXACTLY 1080"// ---- 父容器用它给子 View 生成 spec ----ViewGroup.getChildMeasureSpec(intspec,intpadding,intchildDimension)// ---- 子 View 用它裁剪自己的测量结果 ----View.getDefaultSize(intsize,intmeasureSpec)View.resolveSize(intsize,intmeasureSpec)// 返回纯尺寸View.resolveSizeAndState(intsize,intms,intchildMeasuredState)// 带状态位// ---- 子 View 上报结果 ----View.setMeasuredDimension(intmeasuredWidth,intmeasuredHeight)View.getMeasuredWidth()/getMeasuredHeight()View.getMeasuredState()5. MeasureSpec 从哪来:测量规则的向下传递5.1 树顶:DecorView / ViewRootImpl测量从根节点开始。ViewRootImpl.performMeasure()会拿到窗口可用尺寸,然后调用:privatestaticintgetRootMeasureSpec(intwindowSize,intmeasurement){intmeasureSpec;switch(measurement){caseViewGroup.LayoutParams.MATCH_PARENT:// 撑满窗口 → 硬性要求measureSpec=MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.EXACTLY);break;caseViewGroup.LayoutParams.WRAP_CONTENT:// 最多撑满窗口 → 上限measureSpec=MeasureSpec.makeMeasureSpec(windowSize,MeasureSpec.AT_MOST);break;default:// 具体数值 → 硬性要求measureSpec=MeasureSpec.makeMeasureSpec(measurement,MeasureSpec.EXACTLY);break;}returnmeasureSpec;}这就是"根 MeasureSpec 的生成规则",也是整个映射表的第一行原型:MATCH_PARENT → EXACTLY、WRAP_CONTENT → AT_MOST、固定值 → EXACTLY。5.2 中间层:ViewGroup.getChildMeasureSpec每一个 ViewGroup 在测量自己的孩子时,都要把「自己的 spec」和「孩子的 LayoutParams」合成「孩子的 spec」。这段逻辑被抽取成了公共静态方法:publicstaticintgetChildMeasureSpec(intspec,intpadding,intchildDimension){intspecMode=MeasureSpec.getMode(spec);intspecSize=MeasureSpec.getSize(spec);// 可用空间 = 父给的尺寸 - 已占用的 padding / marginintsize=Math.max(0,specSize-padding);intresultSize=0;intresultMode=0;switch(specMode){// 父容器自己被强制固定大小caseMeasureSpec.EXACTLY:if(childDimension=0){// 子 View 要具体数值,满足它resultSize=childDimension;resultMode=MeasureSpec.
阅读完成 · 觉得有帮助?
咨询建站