compose-animations

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Compose: animations

Compose:动画

Core principle

核心原则

Pick the smallest API that matches the problem: built-in visibility and layout transitions first, then a single animated value, then a shared transition object when several values must move together, then gesture-level or imperative APIs when the framework cannot express the motion.
选择最贴合需求的最小化API:优先使用内置的可见性和布局过渡,其次是单个动画值,当多个值需要同步变化时使用共享过渡对象,当框架无法表达所需动效时再使用手势级或命令式API。

Pick the smallest animation API

选择最小化动画API

NeedAPI
Show or hide a subtree with enter/exit semantics; content is removed after exit completes
AnimatedVisibility
Animate one property toward a target derived from state
animateFloatAsState
/
animateDpAsState
/
animateColorAsState
/
animateOffsetAsState
/ …
Several animated values keyed off one boolean, enum, or sealed state
rememberTransition
+ transition child animations (
animateFloat
,
animateDp
,
animateColor
,
animateValue
, …)
Smooth size when child layout height/width changes (e.g. text wraps)
Modifier.animateContentSize()
Swap between different composable trees for the same slot
AnimatedContent
or
Crossfade
User-driven motion (drag, fling, interruptible springs)
Animatable
and related coroutine APIs (see Advanced pointers)
需求API
为子树添加进入/退出语义的显示/隐藏动画;退出完成后移除内容
AnimatedVisibility
基于状态驱动单个属性向目标值动画
animateFloatAsState
/
animateDpAsState
/
animateColorAsState
/
animateOffsetAsState
/ …
多个动画值由单个布尔值、枚举或密封状态驱动
rememberTransition
+ 子动画(
animateFloat
animateDp
animateColor
animateValue
等)
子布局高度/宽度变化时实现平滑尺寸过渡(如文本换行)
Modifier.animateContentSize()
同一位置切换不同可组合树
AnimatedContent
Crossfade
用户驱动的动效(拖拽、快速滑动、可中断弹簧动画)
Animatable
及相关协程API(参见进阶指引)

Appear and disappear

显示与隐藏

Prefer
AnimatedVisibility
when the UI should leave or join the tree with enter/exit transitions.
kotlin
AnimatedVisibility(visible = expanded) {
    Text("Details…")
}
animateFloatAsState
on alpha
only fades; the composable stays in composition and continues to participate in layout unless you gate it yourself. Use that tradeoff when you intentionally keep children mounted (state, focus) but visually hidden. For true remove-from-tree behavior, use
AnimatedVisibility
(or conditional composition with
AnimatedVisibility
/
AnimatedContent
patterns from the quick guide).
当UI需要通过进入/退出过渡加入或离开组件树时,优先使用
AnimatedVisibility
kotlin
AnimatedVisibility(visible = expanded) {
    Text("Details…")
}
仅对透明度使用
animateFloatAsState
只会实现淡入淡出效果;除非自行控制,否则该可组合项会保留在组合中并继续参与布局。当你有意保持子组件挂载(保留状态、焦点)但视觉上隐藏时,可以使用这种方式。若需要真正从组件树中移除的效果,请使用
AnimatedVisibility
(或参考快速指南中的
AnimatedVisibility
/
AnimatedContent
条件组合模式)。

Background color

背景颜色

Use
animateColorAsState
for smooth color targets.
For animated fills behind children, the quick guide recommends drawing with
Modifier.drawBehind
rather than
Modifier.background()
so the animated color is applied in the draw phase appropriately for performance.
kotlin
val background = animateColorAsState(
    targetValue = if (selected) selectedColor else idleColor,
    label = "background",
)
Box(
    Modifier.drawBehind { drawRect(background.value) },
) { /* content */ }
使用
animateColorAsState
实现平滑的颜色目标值过渡。
对于子组件背后的动画填充,快速指南建议使用**
Modifier.drawBehind
**而非
Modifier.background()
,这样动画颜色会在绘制阶段正确应用,提升性能。
kotlin
val background = animateColorAsState(
    targetValue = if (selected) selectedColor else idleColor,
    label = "background",
)
Box(
    Modifier.drawBehind { drawRect(background.value) },
) { /* content */ }

Size changes

尺寸变化

Modifier.animateContentSize()
animates layout size changes—common for expanding/collapsing text or dynamic chips—without hand-rolling width/height animations.
Modifier.animateContentSize()
可实现布局尺寸变化的动画——常用于展开/折叠文本或动态标签,无需手动编写宽/高动画。

Value-based animations (
animate*AsState
)

基于值的动画(
animate*AsState

Compose provides
animate*AsState
for
Float
,
Dp
,
Color
,
Size
,
Offset
,
Rect
,
Int
,
IntOffset
,
IntSize
, and more. You supply the target; the API owns the animation state.
  • Pass an
    AnimationSpec
    via
    animationSpec
    (e.g.
    spring
    ,
    tween
    ) when defaults are wrong for the UI.
  • Set a distinct
    label
    for debugging and tooling when multiple animations exist in one composable.
  • For completion or sequencing details, see Value-based animations.
kotlin
val width by animateDpAsState(
    targetValue = if (expanded) 200.dp else 56.dp,
    animationSpec = spring(dampingRatio = 0.7f, stiffness = Spring.StiffnessMedium),
    label = "fabWidth",
)
Compose为
Float
Dp
Color
Size
Offset
Rect
Int
IntOffset
IntSize
等类型提供了
animate*AsState
。你只需提供目标值,API会管理动画状态。
  • 当默认设置不符合UI需求时,可通过
    animationSpec
    参数传入
    AnimationSpec
    (如
    spring
    tween
    )。
  • 当一个可组合项中存在多个动画时,设置独特的**
    label
    **便于调试和工具分析。
  • 关于动画完成或序列编排的细节,请参考基于值的动画
kotlin
val width by animateDpAsState(
    targetValue = if (expanded) 200.dp else 56.dp,
    animationSpec = spring(dampingRatio = 0.7f, stiffness = Spring.StiffnessMedium),
    label = "fabWidth",
)

Multiple properties:
rememberTransition

多属性动画:
rememberTransition

When one piece of state (e.g.
enum class Phase { A, B, C }
) should drive several animated values in lockstep, use
rememberTransition
and define child animations on that transition:
kotlin
val transition = rememberTransition(targetState = phase, label = "phase")
val alpha by transition.animateFloat(label = "alpha") { target ->
    if (target == Phase.Visible) 1f else 0f
}
val offset by transition.animateDp(label = "offset") { target ->
    if (target == Phase.Visible) 0.dp else 24.dp
}
Avoid multiple independent
animate*AsState
calls that should stay visually synchronized but can drift if specs or targets diverge. Older code may use
updateTransition
; prefer
rememberTransition
for new code.
当单个状态(如
enum class Phase { A, B, C }
)需要同步驱动多个动画值时,使用
rememberTransition
并在该过渡上定义子动画:
kotlin
val transition = rememberTransition(targetState = phase, label = "phase")
val alpha by transition.animateFloat(label = "alpha") { target ->
    if (target == Phase.Visible) 1f else 0f
}
val offset by transition.animateDp(label = "offset") { target ->
    if (target == Phase.Visible) 0.dp else 24.dp
}
避免使用多个独立的
animate*AsState
调用,即使它们应该保持视觉同步,但如果动画规格或目标值不一致,可能会出现偏差。旧代码可能使用
updateTransition
;新代码请优先使用
rememberTransition

Choosing between content-level APIs

内容级API选择

Use the official Choose an animation API tree when unsure. Compressed rules:
SituationPrefer
Same composable, different target values for layout properties
animate*AsState
or
rememberTransition
Different composable content for the same region (tabs, steps)
AnimatedContent
(custom
transitionSpec
,
contentKey
) or simpler
Crossfade
Pager-like swipe between pagesHorizontal pager APIs from the animation docs / Material—follow the choose-api guidance
Transitions owned by Navigation ComposeUse navigation’s built-in transitions rather than bolting
AnimatedContent
on top of the same destination swap
Art-based motion (illustrations, Lottie, complex vector timelines) is outside this skill; use dedicated libraries and the “additional resources” links on Animations.
不确定时,请参考官方的选择合适的动画API决策树。简化规则如下:
场景优先选择
同一可组合项,布局属性的目标值不同
animate*AsState
rememberTransition
同一区域切换不同可组合内容(标签页、步骤页)
AnimatedContent
(自定义
transitionSpec
contentKey
)或更简单的
Crossfade
类似分页器的页面滑动切换动画文档/ Material库中的水平分页器API——遵循选择API的指引
由Navigation Compose管理的过渡使用导航内置的过渡,不要在相同的目的地切换上额外叠加
AnimatedContent
艺术化动效(插画、Lottie、复杂矢量时间线)不属于本技能范畴;请使用专用库,并参考动画页面的“额外资源”链接。

Decision flow (high level)

决策流程(概览)

mermaid
flowchart TD
  start[Animation_need]
  start --> showHide{Show_or_hide_subtree}
  showHide -->|yes| av[AnimatedVisibility]
  showHide -->|no| oneProp{Single_property_to_target}
  oneProp -->|yes| asState["animate*AsState"]
  oneProp -->|no| multiProp{Many_props_one_state}
  multiProp -->|yes| rt[rememberTransition]
  multiProp -->|no| swapTree{Different_composable_content}
  swapTree -->|yes| ac[AnimatedContent_or_Crossfade]
  swapTree -->|no| advanced[Animatable_or_lower_level]
mermaid
flowchart TD
  start[动画需求]
  start --> showHide{是否显示/隐藏子树}
  showHide -->|是| av[AnimatedVisibility]
  showHide -->|否| oneProp{是否为单个属性目标动画}
  oneProp -->|是| asState["animate*AsState"]
  oneProp -->|否| multiProp{是否为单状态驱动多属性}
  multiProp -->|是| rt[rememberTransition]
  multiProp -->|否| swapTree{是否切换不同可组合内容}
  swapTree -->|是| ac[AnimatedContent或Crossfade]
  swapTree -->|否| advanced[Animatable或更低层级API]

AnimatedContent keys for state holders

AnimatedContent的状态持有者键

When
AnimatedContent
receives a state-holder wrapper such as
AsyncResult<T>
,
Result<T>
, or a sealed
UiState
, decide what should actually trigger the transition. Usually the animation should run when the content shape changes (loading → content → error), not when the payload inside the same shape changes.
Use
contentKey
to map rich state to the animation identity:
kotlin
AnimatedContent(
    targetState = result,
    contentKey = { state ->
        when (state) {
            AsyncResult.Loading -> "loading"
            is AsyncResult.Success -> "content"
            is AsyncResult.Error -> "error"
        }
    },
    label = "profile-content",
) { state ->
    when (state) {
        AsyncResult.Loading -> Loading()
        is AsyncResult.Success -> Profile(state.value)
        is AsyncResult.Error -> ErrorMessage(state.throwable)
    }
}
Without
contentKey
, every unequal
Success(value)
can be treated as new content. That is useful if a payload change should animate, but noisy when fresh data updates the same screen shape.
Choose keys by visual shape:
State changeTypical
contentKey
Loading → Success → ErrorBranch key:
"loading"
,
"content"
,
"error"
Success item A → Success item B should crossfadeStable item id
Success data refresh should update in placeConstant content key for
Success
Error message text changes but error UI shape staysConstant content key for
Error
AnimatedContent
接收状态持有者包装类(如
AsyncResult<T>
Result<T>
或密封类
UiState
)时,需要确定什么情况应该触发过渡。通常,当内容形态变化(加载中→内容→错误)时才应该运行动画,而不是同一形态下的 payload 更新时。
使用
contentKey
将复杂状态映射为动画标识:
kotlin
AnimatedContent(
    targetState = result,
    contentKey = { state ->
        when (state) {
            AsyncResult.Loading -> "loading"
            is AsyncResult.Success -> "content"
            is AsyncResult.Error -> "error"
        }
    },
    label = "profile-content",
) { state ->
    when (state) {
        AsyncResult.Loading -> Loading()
        is AsyncResult.Success -> Profile(state.value)
        is AsyncResult.Error -> ErrorMessage(state.throwable)
    }
}
如果不设置
contentKey
,每个不同的
Success(value)
都会被视为新内容。如果payload变化需要触发动画,这种方式是有用的,但当新数据更新同一屏幕形态时,会产生不必要的动画。
根据视觉形态选择键:
状态变化典型
contentKey
加载中→成功→错误分支键:
"loading"
"content"
"error"
成功项A→成功项B需要淡入淡出稳定的项ID
成功数据刷新需要原地更新
Success
设置常量内容键
错误消息文本变化但错误UI形态不变
Error
设置常量内容键

Animated values and composition performance

动画值与组合性能

animate*AsState
returns
State
that updates frequently. If that value feeds
Modifier.offset
,
Modifier.graphicsLayer
, scroll-adjacent layout, or other frame-rate paths, avoid reading it in the composable body with
by
and then passing it into value-form modifiers—use deferred reads (block modifiers, draw/ layout lambdas) instead. See
compose-state-deferred-reads
.
If recomposition counters spike during motion unrelated to bad stability, see
compose-recomposition-performance
.
animate*AsState
返回的
State
会频繁更新。如果该值用于
Modifier.offset
Modifier.graphicsLayer
、滚动相关布局或其他帧率敏感路径,请避免在可组合项主体中使用
by
读取后传入值形式的修饰符——而是使用延迟读取(块修饰符、绘制/布局lambda)。参见
compose-state-deferred-reads
如果在动效期间重组计数器激增且与稳定性无关,请参考
compose-recomposition-performance

Advanced pointers (read the linked docs)

进阶指引(阅读链接文档)

Common mistakes

常见错误

MistakeFix
Fade with
animateFloatAsState(alpha)
but expect children to unmount
Use
AnimatedVisibility
or remove the subtree from composition when hidden
Three
animateDpAsState
calls that must stay in sync with one enum
One
rememberTransition
+ child animations
Animated color on
Modifier.background
causing extra work
Prefer
drawBehind { drawRect(animatedColor) }
per quick guide
Chaining
LaunchedEffect
+ manual
Animatable
for simple target animation
Prefer
animate*AsState
or
rememberTransition
unless gestures require
Animatable
Ignoring Navigation’s own transitionsUse Nav APIs for destination transitions; do not duplicate with
AnimatedContent
for the same swap
AnimatedContent(targetState = asyncResult)
animates on every data refresh
Add
contentKey
based on the visual shape or stable item identity
错误修复方案
使用
animateFloatAsState(alpha)
实现淡入淡出,但期望子组件卸载
使用
AnimatedVisibility
或在隐藏时将子树从组合中移除
使用三个
animateDpAsState
调用,且它们需要与单个枚举同步
使用一个
rememberTransition
+ 子动画
Modifier.background
上使用动画颜色导致额外性能开销
根据快速指南,优先使用
drawBehind { drawRect(animatedColor) }
为简单的目标动画链式使用
LaunchedEffect
+ 手动
Animatable
优先使用
animate*AsState
rememberTransition
,除非手势动效需要
Animatable
忽略Navigation自身的过渡使用导航API处理目的地过渡;不要在相同的切换上重复使用
AnimatedContent
AnimatedContent(targetState = asyncResult)
在每次数据刷新时都触发动画
根据视觉形态或稳定项标识添加
contentKey

When not to use this skill

不适用本技能的场景

  • Side-effect timing (
    LaunchedEffect
    , clicks launching work): use
    compose-side-effects
    .
  • Deep performance tuning of where snapshot state is read: use
    compose-state-deferred-reads
    as the primary reference.
  • 副作用时机
    LaunchedEffect
    、点击触发任务):使用
    compose-side-effects
  • 快照状态读取的深度性能调优:以
    compose-state-deferred-reads
    为主要参考文档。