Animation and Jetpack Compose

Posted by kwmt27 on Sat, Mar 6, 2021

はじめに

Jetpack Compose: Animation

単一アニメーション

@Composable
fun AnimateAsStateDemo() {
    var blue by remember { mutableStateOf(true) }
    val color = if (blue) Blue else Orange
    Column {
        Button(onClick = { blue = !blue }) {
            Text(text = "CHANGE COLOR")
        }
        Spacer(modifier = Modifier.height(16.dp))

        Box(
            modifier = Modifier
                .size(128.dp)
                .background(color)
        )
    }
}

この例では、BooleanのStateの値blueを持っていて、 ボタンをクリックするとそのStateは反転します。

BoxのカラーがそのBooleanの状態によって切り替わります。

カラーの変更部分をanimateColorAsSate関数でラップすることで、簡単にアニメーションすることができます。(デフォルトはフェードアニメーションなのかな?)

val color by animateColorAsState(if (blue) Blue else Orange)

(初回だけアニメーションしてないように見える)

複数のアニメーションを同時に実行したい

以下はBoxのサイズとカラーの値を同時に変更しています。まだアニメーションはしていません。

private enum class BoxState {
    Small,
    Large
}

@Composable
fun UpdateTransitionDemo() {
    var boxState by remember { mutableStateOf(BoxState.Small) }
    val color = when (boxState) {
        BoxState.Small -> Blue
        BoxState.Large -> Orange
    }
    val size = when (boxState) {
        BoxState.Small -> 64.dp
        BoxState.Large -> 128.dp
    }

    Column {
        Button(onClick = {
            boxState = when (boxState) {
                BoxState.Small -> BoxState.Large
                BoxState.Large -> BoxState.Small
            }
        }) {
            Text(text = "CHANGE COLOR AND SIZE")
        }
        Spacer(modifier = Modifier.height(16.dp))

        Box(
            modifier = Modifier
                .size(size)
                .background(color)
        )
    }
}

このサイズとカラーの値を同時にアニメーションするには?

Transitionを使います。 Transitionを作るには、updateTransition関数を使うことができます。

作成したtranstionをsizeとcolorにそれぞれanimateHogeでラップします。

val transition = updateTransition(targetState = boxState)
val color by transition.animateColor { state ->
    when (state) {
        BoxState.Small -> Blue
        BoxState.Large -> Orange
    }
}
val size by transition.animateDp { state ->
    when (state) {
        BoxState.Small -> 64.dp
        BoxState.Large -> 128.dp
    }
}

アニメーションの振る舞いをカスタムするために、transtionSpecパラメータを指定することができます。 たとえば、 Small -> Largeはゆっくり変化させて、Large -> Smallは早く動かすようにするには、次のようにします。

val size by transition.animateDp(
    transitionSpec = {
        if(targetState == BoxState.Large) {
            tween(durationMillis = 500)
        } else {
            tween(durationMillis = 200)
        }
    }
)

Visibiliy変更のアニメーションするには

@Composable
fun AnimatedVisibilityDemo() {
    var visible by remember { mutableStateOf(true) }
    Column {
        Button(
            onClick = { visible = !visible }
        ) {
            Text(text = if (visible) "HIDE" else "SHOW")
        }
        Spacer(modifier = Modifier.height(16.dp))

        if (visible) {
            Box(
                modifier = Modifier
                    .size(128.dp)
                    .background(Blue)
            )
        }
    }
}

このコードはアニメーションせず、ぱっと切り替わります。

これをアニメーションさせるには?

if(visible)の部分を、

AnimatedVisibility(visible)

にするだけです。(但し、現在は@OptIn(ExperimentalAnimationApi::class)を付ける必要があるようです)

アニメーションするための簡単な方法

@Composable
fun AnimatedContentSizeDemo() {
    var expanded by remember { mutableStateOf(false) }
    Column {
        Button(
            onClick = { expanded = !expanded }
        ) {
            Text(text = if (expanded) "SHRINK" else "EXPAND")
        }
        Spacer(modifier = Modifier.height(16.dp))

        Box(
            modifier = Modifier.background(Color.LightGray)
        ) {
            Text(
                text = "Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
                fontSize = 16.sp,
                textAlign = TextAlign.Justify,
                overflow = TextOverflow.Ellipsis,
                modifier = Modifier.padding(16.dp),
                maxLines = if (expanded) Int.MAX_VALUE else 2
            )
        }
    }
}

これはぱっと切り替わりますが、ModifierにanimateContentSize()をつけるだけで次のようにアニメーションします。

Box(
    modifier = Modifier.background(Color.LightGray)
        .animateContentSize()
)    

クロスフェードアニメーション

private enum class DemoScene {
    Text,
    Icon
}

@Composable
fun CrossFadeDemo() {
    var scene by remember { mutableStateOf(DemoScene.Text) }

    Column {
        Button(
            onClick = {
                scene = when (scene) {
                    DemoScene.Text -> DemoScene.Icon
                    DemoScene.Icon -> DemoScene.Text
                }
            }
        ) {
            Text(text = "TOGGLE")
        }
        Spacer(modifier = Modifier.height(16.dp))

        when (scene) {
            DemoScene.Text -> Text(
                text = "Phone",
                fontSize = 32.sp
            )
            DemoScene.Icon -> Icon(
                imageVector = Icons.Default.Phone,
                contentDescription = null,
                modifier = Modifier.height(48.dp)

            )
        }
    }
}

これをクロスフェードのアニメーションをさせるには、Crossfadeでラップするだけです。

Crossfade(targetState = scene) { scene ->
    when (scene) {
        DemoScene.Text -> Text(
            text = "Phone",
            fontSize = 32.sp
        )
        DemoScene.Icon -> Icon(
            imageVector = Icons.Default.Phone,
            contentDescription = null,
            modifier = Modifier.height(48.dp)
        )
    }
}


comments powered by Disqus