Share
Embed
Compose Transition Animation
DroidKaigi.collect{ #1@Tokyo }
https://droidkaigi.connpass.com/event/277984/
Yuki Anzai
March 31, 2023
Transcript
45 slides
-
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͕ͬͯมΘΔ -
"OJNBUFE$POUFOU w ঢ়ଶʢ4UBUFʣ͕มΘͬͨͱ͖ͷΓସ͑ʢFOUFSFYJUʣΛΞχϝʔγϣϯ ͢Δ @ExperimentalAnimationApi @Composable fun <S> AnimatedContent( targetState:
S, … content: @Composable() AnimatedVisibilityScope.(targetState: S) -> Unit ) { … } -
"OJNBUFE$POUFOU w ঢ়ଶʢ4UBUFʣ͕มΘͬͨͱ͖ͷΓସ͑ʢFOUFSFYJUʣΛΞχϝʔγϣϯ ͢Δ var myState by remember { mutableStateOf(Item.A)
} AnimatedContent( label = "myState", targetState = myState ) { // content lambda ͷҾ (it) Λ͏ // when(myState) μϝ when(it) { ... } } -
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ͷҾΛ͏ -
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 -
"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Λ͍ͬͯΔ -
VQEBUF5SBOTJUJPO w ঢ়ଶʢʣͷมߋʹෳͷΞχϝʔγϣϯΛඥ͚͍ͮͨͱ͖ʹ͏ var item by remember { mutableStateOf(Item.A) }
val itemTransition = updateTransition(targetState = item, label = "item") ঢ়ଶ FOVNDMBTT*UFN\" #^ 5SBOTJUJPOΠϯελϯε͕ฦΔ UBSHFU4UBUFʹ4UBUFͷΛ͢ -
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Λऔಘ͢Δ -
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྆ํΞχϝʔγϣϯ͢Δ -
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) ) -
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Λੜ -
.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Λੜ -
@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 } -
@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 ͋Δʁ -
@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 ͋Δʁ ͋Γ·͢ -
@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 -
.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 } -
.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͔Βߋ৽͞ΕΔ -
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) ) -
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) ) .VUBCMF5SBOTJUJPO4UBUFSFNFNCFS͢Δ -
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Λߋ৽͢Δ -
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Λ͢ -
.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ɺ UBSHFU4UBUFUSVFͳͷͰ Ξχϝʔγϣϯ͕Δ -
.VUBCMF5SBOTJUJPO4UBUFͳΒͰ͖Δ͜ͱ w ࠷ॳͷUBSHFU4UBUFͱผͷॳظঢ়ଶΛͱΕΔ w DPNQPTJUJPOʹೖͬͨͱ͖ʹΞχϝʔγϣϯͤ͞Δ͜ͱ͕Ͱ͖Δ w .VUBCMF5SBOTJUJPO4UBUFΛ࠶ੜ͢Δ͜ͱͰɺΞχϝʔγϣϯͷϦελʔτ ΛτϦΨʔͰ͖Δ val transitionState
= remember(trigger) { MutableTransitionState(false) } val itemTransition = updateTransition(transitionState = transitionState, label = "item") USJHHFS͕มΘΔͱ.VUBCMF5SBOTJUJPO4UBUF͕࡞Γ͞ΕΔͷͰ 5SBOTJUJPO࡞Γ͞ΕΔ -
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Λ͏ -
var item by remember { mutableStateOf(Item.A) } val transitionState =
remember { MutableTransitionState(Item.B) } transitionState.targetState = item val itemTransition = updateTransition( transitionState = transitionState, label = "item" ) … μϝͳ߹ DPNQPTJUJPO࣌ͷΞχϝʔγϣϯͰ #ͷͱ͖ͷ੨͍࢛͕֯Ұॠදࣔ͞Εͯ͠·͏ -
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" ͷΞχϝʔγϣϯ͕Δ -
@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 ) { -
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> … } -
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 } … -
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Λ͏ -
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࣌ͷUBSHFUFYJU࣌ͷॳظ FYJU࣌ͷUBSHFU -
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:ΛΞχϝʔγϣϯ