動畫修飾符和可組合函式

Compose 提供內建的可組合項和修飾詞,可用於處理常見的動畫用途。

內建動畫可組合項

使用 AnimatedVisibility 為出現和消失加入動畫效果

綠色可組合項顯示及隱藏本身
圖 1 為資料欄中項目的外觀和消失加上動畫效果

AnimatedVisibility 可組合項以動畫方式呈現內容的顯示與消失。

var visible by remember {
    mutableStateOf(true)
}
// Animated visibility will eventually remove the item from the composition once the animation has finished.
AnimatedVisibility(visible) {
    // your composable here
    // ...
}

根據預設,內容會以淡入與展開的效果顯示,並且以淡出和縮小方式消失。只要指定 EnterTransitionExitTransition 即可自訂轉場效果。

var visible by remember { mutableStateOf(true) }
val density = LocalDensity.current
AnimatedVisibility(
    visible = visible,
    enter = slideInVertically {
        // Slide in from 40 dp from the top.
        with(density) { -40.dp.roundToPx() }
    } + expandVertically(
        // Expand from the top.
        expandFrom = Alignment.Top
    ) + fadeIn(
        // Fade in with the initial alpha of 0.3f.
        initialAlpha = 0.3f
    ),
    exit = slideOutVertically() + shrinkVertically() + fadeOut()
) {
    Text("Hello", Modifier.fillMaxWidth().height(200.dp))
}

如上例所示,您可以將多個 EnterTransitionExitTransition 物件與 + 運算子合併,並為每個物件納入選用參數來自訂行為。詳情請參閱參考資料。

EnterTransitionExitTransition 範例

EnterTransition ExitTransition
fadeIn
淡入動畫
fadeOut
淡出動畫
slideIn
滑入動畫
slideOut
滑出動畫
slideInHorizontally
水平滑入動畫
slideOutHorizontally
水平滑出動畫
slideInVertically
垂直滑入動畫
slideOutVertically
垂直滑出動畫
scaleIn
向內縮減動畫
scaleOut
向外擴充動畫
expandIn
展開進入動畫
shrinkOut
縮小退出動畫
expandHorizontally
水平展開動畫
shrinkHorizontally
水平縮小動畫
expandVertically
垂直展開動畫
shrinkVertically
垂直縮小動畫

AnimatedVisibility 也提供可包含 MutableTransitionState 的變化版本,可讓您將 AnimatedVisibility 新增至組合樹時立即觸發動畫。這項元件也有助於觀察動畫狀態。

// Create a MutableTransitionState<Boolean> for the AnimatedVisibility.
val state = remember {
    MutableTransitionState(false).apply {
        // Start the animation immediately.
        targetState = true
    }
}
Column {
    AnimatedVisibility(visibleState = state) {
        Text(text = "Hello, world!")
    }

    // Use the MutableTransitionState to know the current animation state
    // of the AnimatedVisibility.
    Text(
        text = when {
            state.isIdle && state.currentState -> "Visible"
            !state.isIdle && state.currentState -> "Disappearing"
            state.isIdle && !state.currentState -> "Invisible"
            else -> "Appearing"
        }
    )
}

為子項建立進入與結束動畫效果

AnimatedVisibility (直接或間接子項) 中的內容可使用 animateEnterExit 輔助鍵為每個子項指定不同的動畫行為。每個子項的視覺效果都是 AnimatedVisibility 可組合項中指定的動畫和子項本身的進入和結束動畫組合而成。

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) {
    // Fade in/out the background and the foreground.
    Box(Modifier.fillMaxSize().background(Color.DarkGray)) {
        Box(
            Modifier
                .align(Alignment.Center)
                .animateEnterExit(
                    // Slide in/out the inner box.
                    enter = slideInVertically(),
                    exit = slideOutVertically()
                )
                .sizeIn(minWidth = 256.dp, minHeight = 64.dp)
                .background(Color.Red)
        ) {
            // Content of the notification…
        }
    }
}

在某些情況下,您可能希望 AnimatedVisibility 完全不套用任何動畫,即可透過 animateEnterExit 為子項個別指定不同的動畫。為此,請在 AnimatedVisibility 可組合項中指定 EnterTransition.NoneExitTransition.None

新增自訂動畫

除了內建的進入與結束動畫以外,如要新增自訂動畫效果,請透過 AnimatedVisibility 內容 lambda 中的 transition 屬性存取基礎 Transition 例項。新增至 Transition 執行個體的任何動畫狀態將會與 AnimatedVisibility 的進入與結束動畫同時執行。AnimatedVisibility 會等待 Transition 中的所有動畫都結束後,才會移除其中內容。如果是與 Transition 分開建立 (例如使用 animate*AsState) 的結束動畫,AnimatedVisibility 將無法將其納入考量,因此可以在其完成前移除內容可組合項。

var visible by remember { mutableStateOf(true) }

AnimatedVisibility(
    visible = visible,
    enter = fadeIn(),
    exit = fadeOut()
) { // this: AnimatedVisibilityScope
    // Use AnimatedVisibilityScope#transition to add a custom animation
    // to the AnimatedVisibility.
    val background by transition.animateColor(label = "color") { state ->
        if (state == EnterExitState.Visible) Color.Blue else Color.Gray
    }
    Box(modifier = Modifier.size(128.dp).background(background))
}

如要進一步瞭解 Transition,請參閱 updateConversion

使用 AnimatedContent 根據目標狀態建立動畫

AnimatedContent 可組合項可根據目標狀態產生變動時,為內容建立動畫效果。

Row {
    var count by remember { mutableStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Add")
    }
    AnimatedContent(targetState = count) { targetCount ->
        // Make sure to use `targetCount`, not `count`.
        Text(text = "Count: $targetCount")
    }
}

請注意,您應一律使用 lambda 參數,並將其反映至內容當中。API 會使用這個值做為索引鍵來識別目前顯示的內容。

根據預設,初始內容會淡出,然後目標內容淡入 (這個行為稱為淡出淡入切換)。只要將 ContentTransform 物件指定至 transitionSpec 參數,即可自訂這個動畫的行為。您可以使用 with infix 函式結合 EnterTransitionExitTransition 來建立 ContentTransform。可以將 SizeTransform 套用至 ContentTransform,做法是將它加至 using infix 函式裡。

AnimatedContent(
    targetState = count,
    transitionSpec = {
        // Compare the incoming number with the previous number.
        if (targetState > initialState) {
            // If the target number is larger, it slides up and fades in
            // while the initial (smaller) number slides up and fades out.
            slideInVertically { height -> height } + fadeIn() with
                slideOutVertically { height -> -height } + fadeOut()
        } else {
            // If the target number is smaller, it slides down and fades in
            // while the initial number slides down and fades out.
            slideInVertically { height -> -height } + fadeIn() with
                slideOutVertically { height -> height } + fadeOut()
        }.using(
            // Disable clipping since the faded slide-in/out should
            // be displayed out of bounds.
            SizeTransform(clip = false)
        )
    }
) { targetCount ->
    Text(text = "$targetCount")
}

EnterTransition 會定義目標內容的顯示方式,而 ExitTransition 則會定義初始內容的消失方式。除了可用於 AnimatedVisibility 的所有 EnterTransitionExitTransition 函式外,AnimatedContent 也提供 slideIntoContainerslideOutOfContainer。這些元件可做為 slideInHorizontally/VerticallyslideOutHorizontally/Vertically 簡便的替代項目,可根據初始內容的大小和 AnimatedContent 內容的目標內容來計算滑動距離。

SizeTransform 用於定義初始內容和目標內容之間動畫效果的大小。建立動畫時,您可以存取初始大小和目標大小,還能透過 SizeTransform 控制在動畫播放期間是否應該將內容裁剪為元件大小。

var expanded by remember { mutableStateOf(false) }
Surface(
    color = MaterialTheme.colorScheme.primary,
    onClick = { expanded = !expanded }
) {
    AnimatedContent(
        targetState = expanded,
        transitionSpec = {
            fadeIn(animationSpec = tween(150, 150)) with
                fadeOut(animationSpec = tween(150)) using
                SizeTransform { initialSize, targetSize ->
                    if (targetState) {
                        keyframes {
                            // Expand horizontally first.
                            IntSize(targetSize.width, initialSize.height) at 150
                            durationMillis = 300
                        }
                    } else {
                        keyframes {
                            // Shrink vertically first.
                            IntSize(initialSize.width, targetSize.height) at 150
                            durationMillis = 300
                        }
                    }
                }
        }
    ) { targetExpanded ->
        if (targetExpanded) {
            Expanded()
        } else {
            ContentIcon()
        }
    }
}

以動畫呈現子項的進入和結束轉場效果

AnimatedVisibility 一樣,AnimatedContent 的內容 lambda 中具有animateEnterExit 修飾元。使用此方法將 EnterAnimationExitAnimation 分別套用至每個直接或間接的子項中。

新增自訂動畫

AnimatedVisibility 一樣,transition 欄位可在 AnimatedContent 的內容 lambda 中使用。只要使用這個元件,即可建立可與 AnimatedContent 轉場同時執行的自訂動畫效果。詳情請參閱「updateTransition」。

使用 Crossfade 在兩個版面配置之間建立動畫

Crossfade 可在兩個版面配置之間建立交叉漸變的動畫效果。透過切換傳遞至 current 參數的值,讓系統以交叉漸變的動畫效果切換顯示內容。

var currentPage by remember { mutableStateOf("A") }
Crossfade(targetState = currentPage) { screen ->
    when (screen) {
        "A" -> Text("Page A")
        "B" -> Text("Page B")
    }
}

內建動畫修飾符

使用 animateContentSize 為可組合元件大小變更加上動畫效果

綠色可組合函式流暢地動畫大小變更。
圖 2. 可組合小尺寸與較大尺寸之間的順暢動畫效果

animateContentSize 修飾詞可用於建立帶有大小變化的動畫效果。

var expanded by remember { mutableStateOf(false) }
Box(
    modifier = Modifier
        .background(colorBlue)
        .animateContentSize()
        .height(if (expanded) 400.dp else 200.dp)
        .fillMaxWidth()
        .clickable(
            interactionSource = remember { MutableInteractionSource() },
            indication = null
        ) {
            expanded = !expanded
        }

) {
}

清單項目動畫

如果您要在 Lazy 清單或格線中為項目重新排序建立動畫,請參閱 Lazy 版面配置項目動畫文件