用途
该条目面向使用 Kotlin 与 Jetpack Compose 的界面开发场景,讨论动画状态应由谁持有。核心主张是:外壳(shell)适合持有“覆盖层控件是否显示”这类布尔状态,但不应同时持有“已经补间好的 alpha 值”或具体动画曲线,否则等于替所有未来的外观(look)做了一次设计决策。
主要观点与能力
- 外壳发布普通布尔值(flag),不使用 Flow 或动画状态,以便在同一次重组中被多个外观读取,并可直接用于 if 比较。
- 每个外观根据该布尔值自行派生 fade,例如在 look 内部用 animateFloatAsState,根据 state.showControls 选择目标值 1f 或 0f。
- 指出对称线性淡入淡出的问题:半透明混合阶段过长,在明亮背景下,带填充容器的控件会在数百毫秒内与背景合成,看起来像颜色变浅的按钮,容易被报告为颜色缺陷;纯文本、无底色图标等仅前景元素不会暴露该问题。
- 推荐修复方式是用非对称曲线,而不是换颜色:在单个 animateFloatAsState 的 spec 中按目标方向写入时长(示例为淡入 180ms、淡出 500ms),两个稳态不变。
- 明确不要用两个 animateFloatAsState 通过 if 切换,因为接管的动画会从当前值起步,导致跳变。
- 建议保留原先共享的补间值供旧外观继续读取,新外观忽略它即可;不要因为“新样式不用”就删除字段,删除会破坏旧样式,文档注释也不能作为无人使用的证据,应 grep 消费者(如 screen-shell-content-split)。
- 不要通过修改共享曲线来“修复”,因为那会改变原有外观的观感,且 diff 只是单个数字,评审时难以察觉;非对称曲线应属于需要它的外观。
- 该规则同样覆盖 easing、delay 和 spring stiffness:任何决定变化被如何感知的内容都属于表现层。外壳可以持有围绕 flag 的状态机,例如自动隐藏计时、点击切换、滚动是否取消,因为这些行为必须在不同外观下保持一致。
使用与验证方式
条目给出两类 grep 验证:一是检索 controlLayoutAlpha|showControlLayout,预期外壳同时计算并交出布尔值与补间值、状态持有者同时声明两者,且消费者数量不同,这种不对称正是模式本身而非泄漏;二是检索 animateFloatAsState( 附近出现 durationMillis = if 的本地派生 fade,以便逐个核对理由。另建议目视验证:在明亮且运动的背景上,把带填充容器的按钮与一个从不淡出的控件并排,反复切换控件并观察过渡中段而非两端;若淡出按钮经过自身容器颜色的较浅色,说明曲线暴露了合成问题。
适用场景与限制
适用于 Compose 中多个外观共享显隐状态、但需要各自控制动画表现的界面架构。条目为实践建议与代码片段,未提供完整项目配置、性能数据或兼容性说明;具体时长数值仅为示例,需按实际设计调整。