Jetpack Compose 自定义 Layout 入门:Measure、Place 与 Constraints 约束传递
本文强调约束Constraints如何在父子间传递、以及「为什么 Row 不够用才写 Layout」。配套示例CustomLayoutLabScreen.kt中HorizontalTapeLayout横向依次 measure → 累加宽度 → placeRelative。1. 两阶段心智Measure父把Constraints传给子子返回自己在该约束下的尺寸普通Layout中同一个Measurable在一次 measure pass 里不要重复measure。Place父拿到Placeable后在自身坐标系里摆放子项z-order 即调用顺序。Layout(content) { measurables, constraints - … }lambda 必须返回layout(width, height) { … }空子项早退也要returnLayout layout(0, 0) {}。2. 约束里的「无界方向」陷阱在滚动容器里滚动方向上的约束可能是无界的垂直滚动常见maxHeight Constraints.Infinity横向滚动常见maxWidth Constraints.Infinity。自定义Layout要明确自己支持哪种无界方向否则容易出现测量失败或尺寸不符合预期。技巧像本示例一样constraints.copy(minWidth 0, minHeight 0)再给子 measure只是在放松最小约束避免子项被父级最小尺寸强制撑开它不会消除maxWidth/maxHeight的无界问题。真正生产里要按设计定义固定尺寸、裁切、滚动、换行或 weight 语义。本示例的HorizontalTapeLayout会把最终width限制在父约束内如果子项总宽度超过父宽后续子项仍会按原宽度继续placeRelative超出部分可能不可见。这是教学 demo 可接受的简化生产组件应明确「裁切 / 横向滚动 / 自动换行」策略。3. 何时别写 Layout需求更优先等分、权重RowModifier.weight链式约束ConstraintLayoutCompose 版重叠对齐Boxalign复杂可复用测量自定义Layout或SubcomposeLayout更强大也更难SubcomposeLayout需要先量内容再决定子项数量/尺寸如 Flow时用性能与调试成本更高评审门槛应更高。4. 避坑清单在 measure 里读 State 副作用会触发额外订阅测量阶段逻辑要纯。忘记coerceInsumOf(width)超出父maxWidth时直接layout(sum, …)可能违反父约束但只coerceIn不等于完成布局策略还要决定超出内容如何处理。Intrinsic 测量width(IntrinsicSize.Min)等会触发额外测量路径列表里滥用会卡。5. 自检清单自定义Layout的measure lambda里是否避免读写会触发订阅的State子项宽度之和超过父maxWidth时是否做了coerceIn并明确裁切、换行或滚动策略而不是直接layout(超宽, …)在滚动方向无界时是否区分竖向滚动的无界高度与横向滚动的无界宽度并按布局语义处理Infinity需求是否真到了非Layout不可是否已排除Rowweight、Box、ConstraintLayout参考答案复习用应避免。measure 阶段保持纯函数需要随状态变的尺寸在组合外层读 state把算好的 constraints 或 flag传入Layout而不是在 measurables 循环里state.value副作用订阅。必须处理。否则违反父约束或裁切异常要么限制每子最大宽、要么sum.coerceAtMost(constraints.maxWidth)后明确截断/裁切或改用可滚动容器、换行布局。必须区分方向。constraints.copy(minWidth 0, minHeight 0)只是放松最小约束遇到maxWidth/maxHeight Constraints.Infinity时应按组件语义限制尺寸、滚动或换行。优先用现成布局。仅当 Flow、胶带横向排列、特殊重叠测量等Row/Column表达不了时再写LayoutSubcomposeLayout更晚引入需额外评审。源码仓库ComposeDemo分支main相关推荐《重组与参数稳定性跳过规则、derivedStateOf 与调试》《测试分层JVM 单测、ViewModel 测试与 Compose UI Test》《Material3 组件选择、状态管理与避坑指南》