動畫修飾符和可組合函式

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 範例

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 { mutableIntStateOf(0) }
    Button(onClick = { count++ }) {
        Text("Add")
    }
    AnimatedContent(
        targetState = count,
        label = "animated content"
    ) { 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() togetherWith
                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() togetherWith
                slideOutVertically { height -> height } + fadeOut()
        }.using(
            // Disable clipping since the faded slide-in/out should
            // be displayed out of bounds.
            SizeTransform(clip = false)
        )
    }, label = "animated content"
) { 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)) togetherWith
                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
                        }
                    }
                }
        }, label = "size transform"
    ) { 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, label = "cross fade") { 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 版面配置項目動畫文件

目前沒有任何建議。

建議 Google 帳戶。