跳到主要内容

ADR-0014:渲染器中立的交互指示

  • 状态:已接受
  • 日期:2026-08-15
  • 扩展:ADR-0013

背景​

ViewCompose 的布局原语和组件 NodeSpec 曾暴露 Android 形态的 rippleColor 属性。部分组件 同时又暴露完整的按下、聚焦和悬停状态层,导致同一反馈存在两条优先级路径。 StateLayerBox 与 StateLayerRow 还把交互反馈伪装成独立布局原语,尽管它们并不改变测量 或放置。

这会把 Android RippleDrawable 的执行模型泄露到 UI Contract,扩大简单布局 DSL,并让 本可通过有序 Modifier 外观增量更新的反馈变化触发 NodeSpec 变化和重新绑定。单一按下色 也无法表达原生复合控件中已选与未选目标的差异。

决策​

UI Contract 提供密封、不可变的 UiInteractionIndication 值,以及有序的 Modifier.interactionIndication 元素。当前 StateLayer 子类型携带完整 UiStateLayerColors;Android Renderer 将其映射为平台状态和遮罩 drawable。新增指示子类型 必须经过明确的兼容性决策,因为渲染器必须处理其依赖版本中的全部子类型。

Box、Row 和其他布局原语只负责测量、放置、子作用域与调用方 Modifier,绝不接收交互 颜色。高层组件先解析语义角色、启用策略、稀疏 overrides 和当前设计系统 recipe,再安装 指示。BasicSurfaceStyle 可以携带完整的已解析指示,但面向应用的稀疏 overrides 仍按 ADR-0013 归组件所有。

主题边界遵循同一模型。UiColors 只包含语义颜色角色,UiStateColors 只包含持久组件状态, 两者都不再暴露 Android 形态的 ripple/controlHighlight 槽位。UiInteractionTokens 统一拥有按下、聚焦和悬停透明度。设计系统适配器可以在内部读取平台 Highlight,但进入 UI Foundation 前必须将它解析为中立交互策略或显式组件指示。

拥有多个内部交互目标的原生后端组件,在自己的 NodeSpec 中携带完整类型值。因此分段控件 和导航目的地保留已选与未选状态层快照。TabRow 发出 eager keyed 子 Box,每个子项通过普通 Modifier 路径拥有自己的指示。

禁用或不可交互的高层组件不安装指示。只有自定义低层节点本身可交互时,缺失指示才可交给 渲染器选择回退。Android ripple 颜色、状态列表构造、遮罩和 drawable 生命周期全部保留为 渲染器私有细节。

这是 alpha 阶段的硬切:直接移除 rippleColor、UiColors.ripple、 UiStateColors.controlHighlight、StateLayerBox 和 StateLayerRow,指示变化使用仅 Modifier 的 patch,不再重新绑定逻辑节点。

结果​

  • 布局 API 保持精简,并可移植到 Android 之外的渲染器。
  • 设计系统解析同一份完整反馈契约,Material 3 不会成为框架策略。
  • 主题快照不能在有效交互透明度策略之外再携带无人消费的 Android Highlight 槽位。
  • 按下、聚焦和悬停优先级可明确测试。
  • 原生多目标控件可分别渲染已选与未选反馈。
  • 自定义组件通过一个有序 Modifier 使用反馈,无需制造伪布局原语。
  • 自定义渲染器必须穷举其 UI Contract 版本提供的指示类型。
  • Modifier 相等性使指示变化 patch 已保留 View,而不重组布局语义或重建节点。

未采用的方案​

保留 rippleColor 兼容回退​

不采用,因为它保留两个事实来源,也无法表达聚焦、悬停和分目标角色。

在每个可点击 NodeSpec 上放置反馈字段​

不采用,因为通用外观属于有序 Modifier 通道,否则每个组件都需要专用 binder 与 differ 逻辑。

保留 StateLayerBox 与 StateLayerRow​

不采用,因为状态层绘制并不定义不同的测量、放置、状态或生命周期原语。

让某一套设计系统拥有指示策略​

不采用,因为 Material 3、One UI 7、应用设计系统和未来 recipe 都是同一中立契约之上的 平级实现。

验证​

契约要求覆盖相等性与 Modifier 顺序、按下/聚焦/悬停状态列表、禁用时缺失、已保留 View 仅 Modifier patch、原生已选/未选目标、多设计系统编译、API dump、可编译示例,并通过源码 门禁拒绝公开 rippleColor 和交互伪布局 API。