尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Jetpack Compose 单次测量和固有特性测量

Jetpack Compose 单次测量和固有特性测量 Jetpack Compose 的单次测量Single-pass Measurement是其 UI 渲染引擎相比于传统 Android View 系统的最核心革新之一。它从根本上解决了传统 UI 树在复杂布局下容易触发的多次测量Multi-pass Measurement和测量指数级膨胀O(2n)O(2^n)O(2n)复杂度问题。一、传统 View 系统的问题在传统的 XML / View 架构中一个 ViewGroup 在测量自身时常常需要对子 View 进行多次测量。1、嵌套权重LinearLayout weight与相对布局RelativeLayout 比如一个带有 layout_weight 的 LinearLayout为了分配剩余空间通常需要对子 View 进行两次测量第一次测量获取 wrap_content 尺寸第二次测量分配权重大小。2、指数级变差 如果这种需要多次测量的布局在层级中发生多层嵌套测量的总次数就会呈指数级暴涨。例如嵌套 5 层 RelativeLayout 可能会导致最深处的 View 被测量25322^5 322532次这会在滑动或动画时造成严重的卡顿Jank。二、Jetpack Compose 如何实现 Single-passCompose 规定在单次布局Layout过程中布局树中的每个 NodeLayoutNode只能被测量measure()一次。Compose 采用自顶向下传递约束自底向上返回尺寸的机制1、Constraints约束 父节点向子节点传递 Constraints包含 minWidth, maxWidth, minHeight, maxHeight 的范围限定。2、Measure测量 子节点根据约束测量自身并计算出自己的尺寸。这个过程在每个子节点上只能发生一次。3、Placeable布局 子节点向父节点返回一个 Placeable 对象包含最终选定的尺寸。4、Placement定位 父节点确定子节点的位置调用 place() 进行摆放。由于约束是向下传递的子节点在测量时就已经知道父节点允许的极限范围因此不需要反复试探和多次测量。三、固有特性测量只测量一次看似很完美但实际场景中经常需要“根据子组件的最大/最小尺寸来决定父组件尺寸”。经典场景 制作一个分割线Divider要求 Divider 的高度和同组文本中“最高的文本”高度一致。如果严格禁止二次测量父组件在测量 Divider 时还不知道文本的最大高度是多少。Compose 如何在保持单次测量原则的前提下解决这一矛盾答案就是 Intrinsic Measurements固有特性测量// 示例使 Row 内的所有子组件适应最大固有高度 Row(modifier Modifier.height(IntrinsicSize.Max)) { Text(Short text) Divider(modifier Modifier.fillMaxHeight().width(1.dp)) Text(Long text that spans\nmultiple lines) }一个 Row 想让所有子组件高度对齐。它需要先知道每个子组件的最小高度取最大值然后才能创建一个固定高度的约束传给所有子组件。但先知道子组件的高度本身就需要查询——这就是固有特性测量的用途。固有特性测量是一个额外的、可选的查询阶段发生在常规测量之前。它不返回 Placeable只返回一个 Int单个维度值不违反单次测量规则。Jetpack Compose 的 UI 渲染引擎与架构设计中还包含了以下几个非常核心且关键的设计原则1、 声明式与重组Declarative Recomposition数据驱动 UIUI f(State) UI 只是当前状态的函数映射。你不需要像传统 View 那样手动调用 setText()或 setVisibility() 来改写控件状态而是只需更新状态Compose 会自动计算并更新 UI。智能/局部位重组Smart / Localized Recomposition 当状态发生变化时Compose会尽可能只重新执行依赖该状态的最小 Composable 函数未受影响的组件会自动跳过Skip。这种精准的“跳过”机制是Compose 保障高性能的关键。2、组合优于继承Composition over Inheritance没有基类包袱 传统 View 系统中任何控件哪怕是一个小图标都必须继承自巨大的 android.view.View 基类拥有数千行代码和大量未用属性。微型函数组合 在 Compose 中不存在像 View 或 ViewGroup 这样的巨型继承树。 UI 元素只是轻量级的 Kotlin 函数。一个复杂的控件由许多微小、单一职责的 Composable 函数如 Text、Icon、Box组合而成极大地提升了灵活度与复用性。3、单向数据流Undirectional Data Flow, UDF状态向下传递事件向上冒泡State Down 父组件将数据状态通过参数传递给子组件。Events Up 子组件触发用户交互事件如点击通过回调函数Lambda 函数向上通知父组件更新状态。好处 避免了传统 UI 中状态分布在各个 View 内部如 EditText 内部存着文本ViewModel 也存着文本导致的状态不一致问题实现了单一事实来源Single Source of Truth。4、阶段分离原则Phases SeparationCompose 在处理帧渲染时将工作严格划分为三个独立的阶段PhasesComposition组合 决定“要显示什么”执行 Composable 函数构建/更新 Layout Tree。Layout布局 决定“放在哪里以及多大”包含 Measure 测量和 Place 放置两个子步骤。Draw绘制 决定“怎么画”将 Canvas 指令渲染到屏幕。跨阶段优化Phase Skipping 如果仅仅是状态变化比如一个随动画改变的颜色或者随滑动滚动的偏移量Compose 可以直接跳过 Composition 和 Layout 阶段直接在 Draw 阶段刷新。这种阶段分离防止了不必要的全流程重新计算。5、显式修饰符系统Modifiers链式装饰替代布局属性 传统 View 的布局属性如 padding、background、onClick混杂在 View 自身属性中。Compose 统一使用 Modifier 链来定义元素的行为、外观和布局约束。顺序敏感Order Sensitivity Modifier 的调用顺序严格决定了其执行顺序。例如Modifier.padding(16.dp).clickable { }内边距区域可点击与 Modifier.clickable { }.padding(16.dp)内边距区域不可点击效果截然不同。这种显式且确定性的设计避免了传统 View 中各属性作用顺序模糊的问题。6、无局限的绘制与无图层设计Layer-less by Default按需创建 RenderNode 在传统 View 中许多 View 会强制分配独立的硬件渲染图层Layer造成严重的显存占用。默认轻量 Compose 中的组件默认只包含绘制指令Canvas API只有当你显式声明需要离屏缓冲、裁剪、旋转或透明度如使用 Modifier.graphicsLayer()时系统才会为其创建独立的绘制图层。这显著降低了内存开销与 GPU 合成成本。7、机制与策略分离Mechanism vs Policy Separation基础组件与业务解耦 Compose 框架将底层渲染机制如 Layout、Draw、Modifier 基础设施与具体的 UI 设计语言策略如 Material Design 2/3彻底分离。高度可定制 开发者如果不想使用 Material 规范完全可以基于最底层的 BasicText 或原生 Layout 构建一套属于自己的全新 UI 系统如 Design System而无需继承或抵消 Material 默认样式。
返回列表