跳到主要内容

迁移 Compose 动画

本文对比 Jetpack Compose Animation、当前 ViewCompose 动画版本和已经接受的扩展契约。 它不是源码兼容承诺。只有生命周期、计时、几何与中断规则一致时,相似名称才表示同一概念。

最后验证日期:2026-08-23

复核责任人:viewcompose-animation-coreviewcompose-animation、Android Renderer、 Navigation、Preview 与 Studio 工具的维护者

基线与证据限制

当前 ViewCompose 目标为:

产物版本当前职责
viewcompose-animation-core0.1.0-alpha04平台中立的时长/物理采样、类型化速度、Mutation、运动策略与显式 Transition 协调
viewcompose-animation0.1.0-alpha04组合所有的物理/状态动画、泛型与 Seekable Transition、可见性、Crossfade 与内容尺寸动画

上游稳定语义基线是 2026-08-12 发布的 Compose Animation 1.12.0。验证依据为官方 Compose Animation 发布说明animation-core API 参考Animatable 参考SeekableTransitionState 参考共享元素指南

仓库中的可执行 Compose 依赖仍为 1.7.8。Compose 1.12.0 还要求 compile SDK 37 和 AGP 9.2,而本仓库当前使用 compile SDK 36 与 AGP 8.13.2。因此:

  1. Android 官方文档确定 1.12.0 的语义对比;
  2. 仓库源码、单测、可编译 Sample、Demo、Preview 与设备测试确定当前 ViewCompose 行为;
  3. 本地 Compose 1.7.8 Fixture 不能证明 1.12.0 语义等价。

ADR-0019 冻结了目标架构与 API 质量等级。ADR-0020 分离了动画值域与速度域。下表标记为 Planned(已规划) 的内容是已接受设计, 不是可用 API;在对应阶段实现、写入所属模块文档并发布前,应用迁移仍应视其为不支持。

能力矩阵

关注点Compose 1.12.0 语义当前 ViewCompose 状态迁移决策
Tween、Keyframes、Snap、Repeat时长与重复 SpecSupported(支持),但 Keyframe/Repeat Surface 更窄移植前复核尚不支持的 Start Offset、Spline Keyframe 与 Path Easing
物理 Spring基于阈值求解,并提供值与速度**Supported(支持)**归一化质量、类型化初速度、解析式阻尼分支、阈值终止与安全上限按 ViewCompose 单位重新调节 Compose 参数;精确时长仍使用 Tween/Keyframes
Animatable 变更所有权后一个变更取消前一调用;完成返回结果状态Supported(支持) Animatable<T, V>、保留速度、结构化结果、Bounds 与 Last-writer 取消保留结构化所有权;取消会抛出异常,不返回 Interrupted 结果
Decay 与 Fling 接力Decay Spec 和速度延续**Supported(支持)**平台中立指数 DecayGesture Owner 仍需在接力前转换 Density、Direction、Axis 与 Nested-scroll 速度
Target-as-state 动画状态驱动的类型化动画**Supported(支持)**泛型值、Float、Int、编码 ARGB 与 UiDp颜色按编码 Channel 插值,不感知色彩空间
Transition共享状态 Segment、泛型 Channel 与 Seek**Supported(支持)**泛型 AnimationConverter<T, V> Channel、稳定的 Segment-aware Spec、唯一自主或 Seeking Writer 与归一化 Seek Progress组合所有目标使用 updateTransition;外部控制则通过 rememberTransition 绑定一个 SeekableTransitionState;公开 Play-time Seek 与 Seek 初速度有意不提供
AnimatedVisibilityEnter/Exit 代数、Slide、Scale 与后代编排Supported(支持) Fade、带 Alignment 的实测 Reveal、按实测比例 Slide、带轴心 Scale、所属 Scope 与共享时钟的后代 Enter/Exit使用 AnimatedVisibilityScope.AnimatedEnterExit,而不是 Compose 的 Modifier 形式;按 Callback 计算 Offset 仍未支持
AnimatedContentKeyed 出入内容替换与 Content TransformSupported(支持) Keyed 替换、逐状态对 Fade/Slide/Scale、Z-order、Alignment 与可选尺寸变换纯 Alpha 替换继续使用 Crossfade;Full-size Callback Offset 与后代 Choreography 尚不支持
内容尺寸动画Layout 尺寸变化Supported(支持),使用 Android Renderer Wrapper 与共享物理 Spring Solver重新验证父级 Constraint 与 Wrapper 位置;无限 Spec 会被拒绝
Bounds 动画跨 Layout 坐标变化的位置与尺寸Supported(支持) Modifier.animateBounds,使用真实 Parent-local Geometry、事务回滚,并保持 Drawing/Input/Accessibility Bounds 一致Bounds Ownership 留在 Renderer;Callback-calculated Lookahead Coordinate 与跨 Window Bounds 仍未支持
共享元素/BoundsScope 内配对与 Overlay 运动**Supported(支持)**类型安全的单 Window Navigation Endpoint、有界 Snapshot、Push/Pop/Replace 与 Predictive Back Complete/CancelNavHost 中使用 Modifier.sharedElement/sharedBounds;跨 Window Pairing、Live Reparenting 与 Shape Morphing 仍未支持
Timeline 检查与 Seek工具可观察并控制合格动画状态Partially supported(部分支持):可选 Preview/Studio 工具执行 Nonce-bound 只读真机 Discovery 与 500 ms Selected Capture;Preview-owned SeekableTransitionState 是唯一 Control Pathviewcompose-preview 只放在 Debug Scope;持续 Profiling 与远程 Seek 真机应用有意不支持

Spring 硬切

Phase 1 之前的 API 接受名义时长:

spring(
dampingRatio = 0.8f,
stiffness = 250f,
durationMillis = 550,
)

该契约不具备 Compose Spring 语义。旧实现把规范化时间映射到阻尼曲线,把进度 Clamp 到 0f..1f,并在指定时长结束;它无法表达真实 Overshoot、速度、平衡或手势延续。

Phase 1 已删除该签名,不保留 Deprecated Overload 或 Alias。迁移必须二选一:

  • 产品行为拥有精确时长时,使用 tween(durationMillis = ..., easing = ...)keyframes(...)
  • 需要物理平衡、Overshoot、中断速度与阈值结束时,使用新的 spring(dampingRatio = ..., stiffness = ...)

只删除 durationMillis 不是机械迁移。旧参数进入的是另一套方程,因此必须针对 Phase 1 物理引擎重新调参。硬切审计覆盖直接构造 SpringSpecMotionScheme Role、 animateContentSizeAnimatableCoreAnimatable、target-as-state、Transition Channel、 Demo 与自定义 Design System。

变更与结果映射

ViewCompose Animatable<T, V> 采用 Last-mutation-wins:不同 Job 的新变更会取消旧调用, 过期帧不能发布,取消后保留最后接受值和速度。它公开 Decay、Bounds 与结构化成功结束结果。

TV 明确分离。Int 位置使用 Float 速度,打包 ARGB Int 值使用有符号四通道 AnimationVelocity 内的 ArgbChannels。自定义 Converter 通过目标 Buffer、稳定维度、零速度与有限正阈值实现 AnimationConverter<T, V>;旧单参数 Converter 没有兼容 Alias。

与简单结果枚举不同的已接受规则包括:

  • 取消仍抛出 CancellationException,不会返回 Interrupted 结果;
  • 正常抵达 Target、碰到 Bound、触发物理安全时长分别返回 FinishedBoundReachedDurationLimitReached
  • initialVelocity = null 会原子捕获替代请求的保留值与速度;显式速度只覆盖捕获的速度;
  • 无效替代请求在所有权变化前失败,不会取消当前 Mutation;
  • 构造会在公开状态前验证初始值与完整 Converter 契约;
  • snapTostop 只发布一次保留零速度的 Final Idle Snapshot,不产生瞬时 Running State; 无效 Snap 仍保留当前 Mutation 的权威性;
  • 成功抵达 Target 会发布精确 Target 与零保留速度。

Gesture 代码以 Converter Domain 的单位/秒移交速度。Density 转换、RTL 符号、Axis 投影与 Nested Scroll 决策属于 Gesture Owner,不属于动画引擎。

Transition 与内容所有权

Compose 迁移应保留以下所有权,而不是只替换调用名:

  • Crossfade 保持纯 Alpha,最多保留 Outgoing 与 Incoming 两份内容;
  • 完整 AnimatedContentcontentKey 定义子树 Identity,在相同父 Constraint 下测量 两个 Child,把 Focus/Input/Accessibility 所有权移交给已提交 Incoming 子树,并在全部 Exit Channel 完成后释放 Outgoing 内容;
  • A-to-B-to-C 替换把已采样 B 提升为 Outgoing 并释放 A,从而最多保留两棵完整子树;
  • 重复 Transform Channel 取该类型最后一次声明;父级/后代 Alpha 相乘,Translation 在 RTL 解析后相加,Scale 围绕各自声明 Origin 相乘;
  • Renderer Apply 失败不能发布候选 Identity、Focus、Geometry、Effect 或 Release。

ViewCompose Visibility 与 Content 的 Slide 距离是参与内容完整实测轴尺寸的非负有限比例,不接收 Full-size Callback 结果。Start/End 根据该 Segment 捕获的布局方向解析。 AnimatedVisibilityScope.transition 暴露所属 Boolean 协调器,AnimatedEnterExit 把后代 Channel 加入同一条 Frame Loop,并延长共享移除生命周期。接受 Exit 会立即移除 Pointer、焦点与 无障碍所有权,同时保留绘制到最后一个 Channel 完成。父级与后代原生 Host 会保持已记录的 Transform 顺序与事务化 Renderer Rollback。

Phase 3 有意把 AnimatedVisibility Content Receiver 从 BoxScope 硬切为 AnimatedVisibilityScope。普通 Builder 调用仍能编译;依赖旧 Receiver 中 Modifier.align 的 调用方必须增加显式 Box,并在 Box 内应用 Alignment。ViewCompose 使用作用域 AnimatedEnterExit Host,而不是 Compose 的后代 Modifier 形式,因为实测 Bounds、裁剪、交互 所有权与 Rollback 都跨越原生 View 边界。不要在可交互内容上用仅绘制 Translation 模拟尚未支持 的 Callback-calculated Offset。

对于 Keyed AnimatedContent,变化 Target 只会在一棵候选树成功 Commit 后接受,因此替换 Segment 前多一个 Commit Boundary,Renderer 失败也无法改变 Content Identity。

Seekable Transition 只有一个 Writer 和一个活动 Composition Binding。seekTo 会在所有权变化 前校验有限 0f..1f Fraction,取消并 Join 旧命令,再把 Fraction 映射到最长已提交 Channel 时长;较短 Channel 会 Clamp 到自身终点。动态新增或移除 Channel 时会保留归一化 Fraction, 并按新的最大值重新采样。Target 变化会把各 Channel 的样本冻结成新起点,Seeking 始终保留零 物理速度。

animateTo 从 Seek Sample 通过唯一自主 Frame Loop 继续,初速度为零;本阶段没有公开初速度 参数。snapTo 不发布 Frame,而是把 Current State、Target State 与两个 Segment Endpoint 折叠为一个 Idle 值。Binding 被移除时会取消活动 Writer,并把未完成 Progress 保留为 Seeking State。该 State 不持有 Scope,不会自动 Save;Predictive Back 的 Commit/Rollback 仍由 Navigation 所有。ViewCompose 暴露归一化 Fraction,而不是公开纳秒 Play-time 控制; Predictive Progress 结束后还必须显式选择 animateTosnapTo,不会反向修改 Navigation Transaction。

Layout 与共享运动映射

Phase 5 Bounds 动画使用完成 RTL 解析后的直接 ViewCompose Layout Parent 物理像素坐标系。 Renderer 会真正 Layout 当前动画矩形,使 Drawing、Hit Test 与 Accessibility Bounds 一致; 纯 Draw Offset 不等价。

Phase 6 有意不移植 Compose 的 SharedTransitionLayoutSharedTransitionScopeAnimatedVisibilityScope 参数。ViewCompose 直接通过 Modifier.sharedElement(SharedContentKey(...))Modifier.sharedBounds(...) 声明类型化端点; 外围 NavHost 已拥有跨 Destination 协调,并消费现有 Committed 或 Predictive-Back Progress。 相同 Key 和 Mode 的一个 Source 与一个 Target 才能配对。重复、缺失、不匹配、Detach、零尺寸、 Surface-backed 或超过预算的 Peer 会按 Key 回退到普通 Destination Motion。Target Destination 拥有 Input、Accessibility 与最终 Focus;不可交互 Snapshot Overlay 只负责共享视觉。Key 不跨 Window、Activity 或 Process 配对;Live-content Reparent 与 Shape Morph 仍不支持。

性能与验证基线

AnimationPerformanceBenchmark 在 Phase 1 前冻结四项 Revision-1 Workload:

场景隔离的当前行为测量动作
animation.specs@1Float、Int、编码 Color 与 UiDp 的固定时长 Spring选择 Spring 后执行四次前进/返回 Target 往返
animation.content@1纯 Alpha Crossfade四次前进/返回内容替换
animation.content-size@1Wrapper 支撑的测量尺寸动画四次 Expand/Collapse 往返
animation.transition@1同步多 Channel Transition四次前进/返回状态 Segment

全部采用五次 CompilationMode.None 迭代、R8/资源收缩的不可调试 Target、测量外五秒启动 稳定期、Accessibility Action、完整动画稳定窗口,并记录 Frame CPU 与进程 Peak Memory。 正式对比要求同一真机、固定 CPU/GPU/Interconnect 策略、相同 Workload Revision、Build Mode、 刷新率与起始 Thermal 状态,且 run-P50 变异系数不超过 0.15

Phase 0 的 Xiaomi MI 6 / API 28 运行接收了全部四项绝对基线;各迭代帧数完全一致,Run-P50 CV 为 0.010..0.075。该批次没有 Candidate 或同轮 Compose Control,因此归一化结论为 inconclusive,不能作为性能改善声明。精确 Percentile、Heap、APK 身份、温度局限和下一步记录在 性能文档第 2.4.8 节

冻结门禁不只检查视觉:Frame CPU P50 仅在同时超过 5% 与 0.3 ms 时失败,P95 仅在同时 超过 10% 与 0.8 ms 时失败;Peak Process Memory 仅在同时超过 10% 与 1,024 KiB 时失败。 引擎 Vector 与 Scratch Buffer 每次 Run 只分配一次;内容保留、额外 Measure、Overlay 释放与 非活跃工具也有结构 Counter。没有解释过的同设备对比,Raw Output 不能关闭阶段。

Phase 2 只把 Content Fixture 推进到 animation.content@2。Primary Action 测量包含 Fade、 Slide、Scale、Clipping 与不等高 Size Transform 的 Keyed AnimatedContent;Secondary Action 在同页保留纯 Alpha Crossfade Control。Xiaomi 固定频率批次中,AnimatedContent 相对 Crossfade 的 P50/P95/Peak Heap 变化为 -1.6%/+7.5%/+3.9%,P95 绝对增量为 +0.651 ms,Heap 增量为 +312 KiB。两者均未越过回退预算,两个 Run-P50 CV 都低于 0.01,解释后的结论为 no material change。精确数值与局限记录在 性能文档第 2.4.10 节

Phase 3 把 animation.core 推进到 Revision 3,并有意提高 Visibility Demo 复杂度:父级 Fade、 逻辑 Slide、Pivot Scale 与对齐 Reveal 和反向后代 Transition 共用一个时钟。在同一次 Xiaomi 固定频率批次中,相对已合并的 Pre-Phase-3 发版安全 Control,Candidate 的 P50/P95/Peak Heap 变化为 +2.4%/+3.3%/+3.9%,即 +0.197 ms/+0.355 ms/+303 KiB,均未越过冻结门禁。P99 增加 3.380 ms15.723 ms,并作为 Tail Watch Item 明确保留。由于可见 Workload 与时长并不 相同,这属于 no material change 发版安全证据,不是严格同负载的 Throughput 或功耗声明。 精确数据、控制条件与局限记录在 性能文档第 2.4.11 节

Phase 4 只把 Transition Fixture 推进到 animation.transition@2。它测量一个泛型二维 Channel 和四个不同时长的类型化 Channel 从归一化 Seek 接力到自主完成。小米 Root 固定频率批次的帧数 均为 200/200/200/200/200,Frame CPU P50/P95/P99 为 7.775/10.493/11.718 ms,Peak Heap 中位数为 8,474 KiB,run-P50 CV 为 0.011,Thermal Throttle Sleep 为零。这是新 Workload 已接受的绝对基线。它不会与控制、Action Path、Channel Set 和 Revision 均不同的 animation.transition@1 做纵向比较。精确 APK Identity、温度、Clock 校验、局限与下一步记录在 性能文档第 2.4.12 节

迁移顺序

  1. 盘点所有当前 SpringSpecspringAnimationSpec、Converter、Animatable、 Transition、Visibility、CrossfadeanimateContentSize 使用。
  2. 把每项动画分类为精确时长、物理、Decay、Keyed Replacement、Visibility、Seek、Bounds 或 Shared Motion。不要仅因某能力当前可用就用它编码另一类别。
  3. 只在 Alpha 与 Duration API 的文档语义足够时保留它们。共享运动迁移等待所属 Phase 发布。
  4. 在同一个改动中硬切所有 Duration-Spring 与单值域 Converter 调用,并按物理方程、值/速度 Domain、阈值、Reduced Motion 与结束结果重新调参。
  5. 按页面所用能力验证 Cancellation、Rapid Retarget、Host Detach、Renderer Failure、RTL、 Focus、Input、Accessibility 与 Reduced Motion。
  6. 修改 Frame、Measure、Retention、Overlay 或 Tooling 路径前后,运行匹配 Revision 的真机 Benchmark。

迁移不会把 Compose Runtime 或 Animation 依赖引入 ViewCompose 生产产物。