アニメーションの修飾子とコンポーザブル

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)
    )
}

上記の例に示すように、複数の EnterTransition オブジェクトまたは ExitTransition オブジェクトを + 演算子で結合できます。各オブジェクトは、動作をカスタマイズするためのオプション パラメータを受け入れます。詳細については、それぞれのリファレンスをご覧ください。

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 のコンテンツ ラムダ内の transition プロパティを使用して、元となる Transition インスタンスにアクセスします。Transition インスタンスに追加されたアニメーションの状態は、AnimatedVisibility の開始アニメーションと終了アニメーションと同時に実行されます。AnimatedVisibility は、Transition 内のすべてのアニメーションが終了するまで待ってから、コンテンツを削除します。(animate*AsState を使用するなどして)Transition とは独立して作成された終了アニメーションについては、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 の詳細については、updateTransition をご覧ください。

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")
    }
}

必ずラムダ パラメータを使用し、それをコンテンツに反映する必要があります。API はこの値をキーとして使用して、現在表示されているコンテンツを識別します。

デフォルトでは、初期コンテンツがフェードアウトしてから、ターゲット コンテンツがフェードインします(この動作はフェードスルーと呼ばれています)。このアニメーションの動作をカスタマイズするには、transitionSpec パラメータに ContentTransform オブジェクトを指定します。ContentTransform を作成するには、with 中置関数を使用して EnterTransitionExitTransition を組み合わせます。SizeTransformContentTransform に適用するには、using 中置関数で接続します。

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 で使用可能なすべての EnterTransition 関数と ExitTransition 関数に加えて、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 と同様に、animateEnterExit 修飾子は AnimatedContent のコンテンツ ラムダ内で使用できます。これを使用して、EnterAnimationExitAnimation を直接または間接の子のそれぞれに個別に適用します。

カスタム アニメーションを追加する

AnimatedVisibility と同様に、transition フィールドは AnimatedContent のコンテンツ ラムダ内で使用できます。これを使用して、AnimatedContent 遷移と同時に実行されるカスタム アニメーション効果を作成します。詳細については、updateTransition をご覧ください。

Crossfade を使用して 2 つのレイアウト間をアニメーション化する

Crossfade は、クロスフェード アニメーションで 2 つのレイアウト間をアニメーション化します。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 グリッド内でアイテムの並べ替えをアニメーション化する場合は、Lazy レイアウト アイテムのアニメーションに関するドキュメントをご覧ください。

現在、おすすめはありません。

Google アカウントにしてください。