Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Compose Transition Animation

Compose Transition Animation

Yuki Anzai

March 31, 2023
Tweet

More Decks by Yuki Anzai

Other Decks in Technology

Transcript

  1. var item by remember { mutableStateOf(Item.A) } Box( contentAlignment =

    Alignment.Center, modifier = Modifier.fillMaxWidth().height(300.dp) ) { Spacer( modifier = Modifier .size(200.dp) .background( when (item) { Item.A -> Color.Red Item.B -> Color.Blue } ) ) } Column(modifier = Modifier.padding(horizontal = 16.dp)) { Item.values().forEach { RadioButtonWithText( text = it.name, selected = item == it, onClick = { item = it } ) } } ᶃJUFN͕มΘΔ ᶄSFDPNQPTF͕૸ͬͯมΘΔ
  2. "OJNBUFE$POUFOU w ঢ়ଶʢ4UBUFʣ͕มΘͬͨͱ͖ͷ੾Γସ͑ʢFOUFSFYJUʣΛΞχϝʔγϣϯ ͢Δ var myState by remember { mutableStateOf(Item.A)

    } AnimatedContent( label = "myState", targetState = myState ) { // content lambda ͷҾ਺ (it) Λ࢖͏ // when(myState) ͸μϝ when(it) { ... } }
  3. var item by remember { mutableStateOf(Item.A) } AnimatedContent( label =

    "item", targetState = item, ) { Box( contentAlignment = Alignment.Center, modifier = Modifier .fillMaxWidth() .height(300.dp) ) { Spacer( modifier = Modifier .size(200.dp) .background( when (it) { Item.A -> Color.Red Item.B -> Color.Blue } ) ) } } … "OJNBUFE$POUFOUΛ࢖͏ DPOUFOUMBNCEBͷҾ਺Λ࢖͏
  4. var item by remember { mutableStateOf(Item.A) } AnimatedContent( label =

    "item", targetState = item, transitionSpec = { fadeIn(tween(300, 150)) + slideIntoContainer( towards = when (targetState) { Item.A -> SlideDirection.End Item.B -> SlideDirection.Start }, initialOffset = { it / 4 }, animationSpec = tween(1000, 150) ) with fadeOut(tween(150)) } ) { … } … NTͰGBEFPVU NT଴ͬͨ͋ͱʢGBEFPVU͕ऴΘͬͨޙʣ NTͰGBEFJO NT଴ͬͨ͋ͱ ʢGBEFPVU͕ऴΘͬͨޙʣ NTͰTMJEFJO
  5. "OJNBUFE$POUFOUͷத਎ΛΈΔͱʜ @ExperimentalAnimationApi @Composable fun <S> AnimatedContent( targetState: S, … )

    { val transition = updateTransition(targetState = targetState, label = label) transition.AnimatedContent( modifier, transitionSpec, contentAlignment, content = content ) } VQEBUF5SBOTJUJPO Λ࢖͍ͬͯΔ 5SBOTJUJPOͷ"OJNBUFE$POUFOUΛ࢖͍ͬͯΔ
  6. VQEBUF5SBOTJUJPO w ঢ়ଶʢ஋ʣͷมߋʹෳ਺ͷΞχϝʔγϣϯΛඥ͚͍ͮͨͱ͖ʹ࢖͏ var item by remember { mutableStateOf(Item.A) }

    val itemTransition = updateTransition(targetState = item, label = "item") ঢ়ଶ FOVNDMBTT*UFN\" #^ 5SBOTJUJPOΠϯελϯε͕ฦΔ UBSHFU4UBUFʹ4UBUFͷ஋Λ౉͢
  7. VQEBUF5SBOTJUJPO w ঢ়ଶʢ஋ʣͷมߋʹෳ਺ͷΞχϝʔγϣϯΛඥ͚͍ͮͨͱ͖ʹ࢖͏ var item by remember { mutableStateOf(Item.A) }

    val itemTransition = updateTransition(targetState = item, label = "item") val size by itemTransition.animateDp(label = "size") { when (it) { Item.A -> 100.dp Item.B -> 200.dp } } val color by itemTransition.animateColor(label = "color") { when (it) { Item.A -> Color.Red Item.B -> Color.Blue } } 5SBOTJUJPOBOJNBUF ͰΞχϝʔγϣϯͷ4UBUF5Λऔಘ͢Δ
  8. var item by remember { mutableStateOf(Item.A) } val itemTransition =

    updateTransition(targetState = item, label = "item") val size by itemTransition.animateDp(label = "size") { when (it) { Item.A -> 100.dp Item.B -> 200.dp } } val color by itemTransition.animateColor(label = "color") { when (it) { Item.A -> Color.Red Item.B -> Color.Blue } } Spacer( modifier = Modifier .size(size) .background(color) ) JUFN͕มΘΔͱTJ[Fͱ CBDLHSPVOE྆ํΞχϝʔγϣϯ͢Δ
  9. var item by remember { mutableStateOf(Item.A) } val itemTransition =

    updateTransition(targetState = item, label = "item") val size by itemTransition.animateDp(label = "size") { when (it) { Item.A -> 100.dp Item.B -> 200.dp } } val color by itemTransition.animateColor(label = "color") { when (it) { Item.A -> Color.Red Item.B -> Color.Blue } } Spacer( modifier = Modifier .size(size) .background(color) )
  10. VQEBUF5SBOTJUJPO ͷத਎ΛΈΔͱʜ @Composable fun <T> updateTransition( targetState: T, label: String?

    = null ): Transition<T> { val transition = remember { Transition(targetState, label = label) } transition.animateTo(targetState) DisposableEffect(transition) { … } return transition } 5SBOTJUJPOΛੜ੒
  11. .VUBCMF5SBOTJUJPO4UBUFΛอ࣋ @Composable fun <T> updateTransition( targetState: T, label: String? =

    null ): Transition<T> { val transition = remember { Transition(targetState, label = label) } transition.animateTo(targetState) DisposableEffect(transition) { … } return transition } @Stable class Transition<S> @PublishedApi internal constructor( private val transitionState: MutableTransitionState<S>, val label: String? = null ) { internal constructor( initialState: S, label: String? ) : this(MutableTransitionState(initialState), label) … } 5SBOTJUJPOΛੜ੒
  12. @Stable class Transition<S> @PublishedApi internal constructor( private val transitionState: MutableTransitionState<S>,

    val label: String? = null ) { internal constructor( initialState: S, label: String? ) : this(MutableTransitionState(initialState), label) … } JOJUJBM4UBUFͰ.VUBCMF5SBOTJUJPO4UBUFΛੜ੒ @Composable fun <T> updateTransition( targetState: T, label: String? = null ): Transition<T> { val transition = remember { Transition(targetState, label = label) } transition.animateTo(targetState) DisposableEffect(transition) { … } return transition }
  13. @Stable class Transition<S> @PublishedApi internal constructor( private val transitionState: MutableTransitionState<S>,

    val label: String? = null ) { internal constructor( initialState: S, label: String? ) : this(MutableTransitionState(initialState), label) … } @Composable fun <T> updateTransition( targetState: T, label: String? = null ): Transition<T> { val transition = remember { Transition(targetState, label = label) } transition.animateTo(targetState) DisposableEffect(transition) { … } return transition } ͬͪ͜ͷίϯετϥΫλΛ࢖͏VQEBUF5SBOTJUJPO ΋͋Δʁ
  14. @Stable class Transition<S> @PublishedApi internal constructor( private val transitionState: MutableTransitionState<S>,

    val label: String? = null ) { internal constructor( initialState: S, label: String? ) : this(MutableTransitionState(initialState), label) … } @Composable fun <T> updateTransition( targetState: T, label: String? = null ): Transition<T> { val transition = remember { Transition(targetState, label = label) } transition.animateTo(targetState) DisposableEffect(transition) { … } return transition } ͬͪ͜ͷίϯετϥΫλΛ࢖͏VQEBUF5SBOTJUJPO ΋͋Δʁ ͋Γ·͢
  15. @Stable class Transition<S> @PublishedApi internal constructor( private val transitionState: MutableTransitionState<S>,

    val label: String? = null ) { internal constructor( initialState: S, label: String? ) : this(MutableTransitionState(initialState), label) … } @Composable fun <T> updateTransition( transitionState: MutableTransitionState<T>, label: String? = null ): Transition<T> { val transition = remember(transitionState) { Transition(transitionState = transitionState, label) } transition.animateTo(transitionState.targetState) DisposableEffect(transition) { … } return transition } .VUBCMF5SBOTJUJPO4UBUFΛͱΔVQEBUF5SBOTBDUJPO
  16. .VUBCMF5SBOTJUJPO4UBUF͕มΘͬͨΒ5SBOTJUJPO͸ ੜ੒͠௚͞ΕΔ .VUBCMF5SBOTJUJPO4UBUFͷUBSHFU4UBUFΛ BOJNBUF5P ʹ౉͢ @Composable fun <T> updateTransition( transitionState:

    MutableTransitionState<T>, label: String? = null ): Transition<T> { val transition = remember(transitionState) { Transition(transitionState = transitionState, label) } transition.animateTo(transitionState.targetState) DisposableEffect(transition) { … } return transition }
  17. .VUBCMF5SBOTJUJPO4UBUF class MutableTransitionState<S>(initialState: S) { var currentState: S by mutableStateOf(initialState)

    internal set var targetState: S by mutableStateOf(initialState) val isIdle: Boolean get() = (currentState == targetState) && !isRunning internal var isRunning: Boolean by mutableStateOf(false) } 5SBOTJUJPO͔Βߋ৽͞ΕΔ ͜ΕΛมߋ͢Δͱ5SBOTJUJPOUBSHFU4UBUF͕ ߋ৽͞ΕͯΞχϝʔγϣϯ͕։࢝͞ΕΔ ʢΞχϝʔγϣϯऴྃ࣌ͳͲʣ5SBOTJUJPO͕৽͍͠ ঢ়ଶʹͳͬͨͱ͖ʹ5SBOTJUJPO͔Βߋ৽͞ΕΔ
  18. var item by remember { mutableStateOf(Item.A) } val transitionState =

    remember { MutableTransitionState(item) } transitionState.targetState = item val itemTransition = updateTransition(transitionState = transitionState, label = "item") val itemTransition = updateTransition(targetState = item, label = "item") val size by itemTransition.animateDp(label = "size") { … } val color by itemTransition.animateColor(label = "color") { … } Spacer( modifier = Modifier .size(size) .background(color) )
  19. var item by remember { mutableStateOf(Item.A) } val transitionState =

    remember { MutableTransitionState(item) } transitionState.targetState = item val itemTransition = updateTransition(transitionState = transitionState, label = "item") val itemTransition = updateTransition(targetState = item, label = "item") val size by itemTransition.animateDp(label = "size") { … } val color by itemTransition.animateColor(label = "color") { … } Spacer( modifier = Modifier .size(size) .background(color) ) .VUBCMF5SBOTJUJPO4UBUF͸SFNFNCFS͢Δ
  20. var item by remember { mutableStateOf(Item.A) } val transitionState =

    remember { MutableTransitionState(item) } transitionState.targetState = item val itemTransition = updateTransition(transitionState = transitionState, label = "item") val itemTransition = updateTransition(targetState = item, label = "item") val size by itemTransition.animateDp(label = "size") { … } val color by itemTransition.animateColor(label = "color") { … } Spacer( modifier = Modifier .size(size) .background(color) ) .VUBCMF5SBOTJUJPO4UBUFͷUBSHFU4UBUFΛߋ৽͢Δ
  21. var item by remember { mutableStateOf(Item.A) } val transitionState =

    remember { MutableTransitionState(item) } transitionState.targetState = item val itemTransition = updateTransition(transitionState = transitionState, label = "item") val itemTransition = updateTransition(targetState = item, label = "item") val size by itemTransition.animateDp(label = "size") { … } val color by itemTransition.animateColor(label = "color") { … } Spacer( modifier = Modifier .size(size) .background(color) ) VQEBUF5SBOTJUJPO ʹ.VUBCMF5SBOTJUJPO4UBUFΛ౉͢
  22. .VUBCMF5SBOTJUJPO4UBUFͳΒͰ͖Δ͜ͱ w ࠷ॳͷUBSHFU4UBUFͱ͸ผͷॳظঢ়ଶΛͱΕΔ w DPNQPTJUJPOʹೖͬͨͱ͖ʹΞχϝʔγϣϯͤ͞Δ͜ͱ͕Ͱ͖Δ var visible by remember {

    mutableStateOf(true) } val transitionState = remember { MutableTransitionState(false) } transitionState.targetState = visible val itemTransition = updateTransition(transitionState = transitionState, label = "item") DPNQPTJUJPO࣌ʹ ॳظঢ়ଶ͸GBMTFɺ UBSHFU4UBUF͸USVFͳͷͰ Ξχϝʔγϣϯ͕૸Δ
  23. .VUBCMF5SBOTJUJPO4UBUFͳΒͰ͖Δ͜ͱ w ࠷ॳͷUBSHFU4UBUFͱ͸ผͷॳظঢ়ଶΛͱΕΔ w DPNQPTJUJPOʹೖͬͨͱ͖ʹΞχϝʔγϣϯͤ͞Δ͜ͱ͕Ͱ͖Δ w .VUBCMF5SBOTJUJPO4UBUFΛ࠶ੜ੒͢Δ͜ͱͰɺΞχϝʔγϣϯͷϦελʔτ ΛτϦΨʔͰ͖Δ val transitionState

    = remember(trigger) { MutableTransitionState(false) } val itemTransition = updateTransition(transitionState = transitionState, label = "item") USJHHFS͕มΘΔͱ.VUBCMF5SBOTJUJPO4UBUF͕࡞Γ௚͞ΕΔͷͰ 5SBOTJUJPO΋࡞Γ௚͞ΕΔ
  24. var item by remember { mutableStateOf(Item.A) } val transitionState =

    remember { MutableTransitionState(item) } transitionState.targetState = item val itemTransition = updateTransition( transitionState = transitionState, label = "item" ) itemTransition.AnimatedContent( transitionSpec = { … } ) { … } VQEBUF5SBOTJUJPO Λ࢖͏ 5SBOTJUJPOͷ"OJNBUFE$POUFOUΛ࢖͏
  25. var item by remember { mutableStateOf(Item.A) } val transitionState =

    remember { MutableTransitionState(Item.B) } transitionState.targetState = item val itemTransition = updateTransition( transitionState = transitionState, label = "item" ) … μϝͳ৔߹ DPNQPTJUJPO࣌ͷΞχϝʔγϣϯͰ #ͷͱ͖ͷ੨͍࢛͕֯Ұॠදࣔ͞Εͯ͠·͏
  26. var item by remember { mutableStateOf(Item.A) } val transitionState =

    remember { MutableTransitionState<Item?>(null) } transitionState.targetState = item val itemTransition = updateTransition( transitionState = transitionState, label = "item" ) itemTransition.AnimatedContent( transitionSpec = { … } ) { Box(…) { if (it != null) { Spacer(…) } } } ॳظঢ়ଶΛOVMMʹ͢Δ DPNQPTJUJPO࣌ʹ͸ OVMMˠ*UFN" ͷΞχϝʔγϣϯ͕૸Δ
  27. @ExperimentalAnimationApi @Composable fun <S> Transition<S>.AnimatedContent( modifier: Modifier = Modifier, transitionSpec:

    AnimatedContentScope<S>.() -> ContentTransform = { fadeIn(animationSpec = tween(220, delayMillis = 90)) + scaleIn(initialScale = 0.92f, animationSpec = tween(220, delayMillis = 90)) with fadeOut(animationSpec = tween(90)) }, contentAlignment: Alignment = Alignment.TopStart, contentKey: (targetState: S) -> Any? = { it }, content: @Composable() AnimatedVisibilityScope.(targetState: S) -> Unit ) {
  28. interface AnimatedVisibilityScope { /** * [transition] allows custom enter/exit animations

    to be specified. It will run simultaneously * with the built-in enter/exit transitions specified in [AnimatedVisibility]. */ @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET") @get:ExperimentalAnimationApi @ExperimentalAnimationApi val transition: Transition<EnterExitState> … }
  29. itemTransition.AnimatedContent( … ) { val rotationY by transition.animateFloat( label =

    "rotationY", transitionSpec = { tween(durationMillis = 1000, delayMillis = 150) } ) { state -> when (state) { EnterExitState.PreEnter -> when (it) { Item.A -> 45f Item.B, null -> -45f } EnterExitState.Visible -> 0f EnterExitState.PostExit -> 0f } } Box(…) { if (it != null) { Spacer( modifier = Modifier .size(200.dp) .graphicsLayer { this.rotationY = rotationY this.cameraDistance = 40f } …
  30. itemTransition.AnimatedContent( … ) { val rotationY by transition.animateFloat( label =

    "rotationY", transitionSpec = { tween(durationMillis = 1000, delayMillis = 150) } ) { state -> when (state) { EnterExitState.PreEnter -> when (it) { Item.A -> 45f Item.B, null -> -45f } EnterExitState.Visible -> 0f EnterExitState.PostExit -> 0f } } Box(…) { if (it != null) { Spacer( modifier = Modifier .size(200.dp) .graphicsLayer { this.rotationY = rotationY this.cameraDistance = 40f } … "OJNBUFE7JTJCJMJUZ4DPQFͷUSBOTJUJPOͰ BOJNBUF99Λ࢖͏
  31. itemTransition.AnimatedContent( … ) { val rotationY by transition.animateFloat( label =

    "rotationY", transitionSpec = { tween(durationMillis = 1000, delayMillis = 150) } ) { state -> when (state) { EnterExitState.PreEnter -> when (it) { Item.A -> 45f Item.B, null -> -45f } EnterExitState.Visible -> 0f EnterExitState.PostExit -> 0f } } Box(…) { if (it != null) { Spacer( modifier = Modifier .size(200.dp) .graphicsLayer { this.rotationY = rotationY this.cameraDistance = 40f } … FOUFS࣌ͷॳظ஋ FOUFS࣌ͷUBSHFU஋FYJU࣌ͷॳظ஋ FYJU࣌ͷUBSHFU஋
  32. itemTransition.AnimatedContent( … ) { val rotationY by transition.animateFloat( label =

    "rotationY", transitionSpec = { tween(durationMillis = 1000, delayMillis = 150) } ) { state -> when (state) { EnterExitState.PreEnter -> when (it) { Item.A -> 45f Item.B, null -> -45f } EnterExitState.Visible -> 0f EnterExitState.PostExit -> 0f } } Box(…) { if (it != null) { Spacer( modifier = Modifier .size(200.dp) .graphicsLayer { this.rotationY = rotationY this.cameraDistance = 40f } … SPUBUJPO:ΛΞχϝʔγϣϯ