### Starting Animations on Composable Launch Source: https://developer.android.com/develop/ui/compose/animation/quick-guide?hl=zh-CN Demonstrates how `LaunchedEffect` can be used to start animations when a composable enters the composition, using `Animatable` and `animateTo`. ```kotlin val alphaAnimation = remember { Animatable(0f) } LaunchedEffect(Unit) { alphaAnimation.animateTo(1f) } Box( modifier = Modifier.graphicsLayer { alpha = alphaAnimation.value } ) AnimationQuickGuide.kt ``` -------------------------------- ### Shared Element Transition Example Source: https://developer.android.com/develop/ui/compose/animation/shared-elements?hl=pl This example demonstrates how to use Modifier.sharedBounds() for shared element transitions, highlighting the importance of modifier order. ```kotlin var selectFirst by remember { mutableStateOf(true) } val key = remember { Any() } SharedTransitionLayout( Modifier .fillMaxSize() .padding(10.dp) .clickable { selectFirst = !selectFirst } ) { AnimatedContent(targetState = selectFirst, label = "AnimatedContent") { targetState -> if (targetState) { Box( Modifier .padding(12.dp) .sharedBounds( rememberSharedContentState(key = key), animatedVisibilityScope = this@AnimatedContent ) .border(2.dp, Color.Red) ) { Text( "Hello", fontSize = 20.sp ) } } else { Box( Modifier .offset(180.dp, 180.dp) .sharedBounds( rememberSharedContentState( key = key, ), animatedVisibilityScope = this@AnimatedContent ) .border(2.dp, Color.Red) // This padding is placed after sharedBounds, but it doesn't match the // other shared elements modifier order, resulting in visual jumps .padding(12.dp) ) { Text( "Hello", fontSize = 36.sp ) } } } } BasicSharedElementSnippets.kt ``` -------------------------------- ### Navigation 2 Source: https://developer.android.com/develop/ui/compose/animation/shared-elements/navigation?hl=de Example of using shared element transitions with Navigation 2. ```kotlin @Composable fun SharedElement_Nav2() { SharedTransitionLayout { val navController = rememberNavController() NavHost( navController = navController, startDestination = "home", modifier = Modifier.safeDrawingPadding() ) { composable("home") { HomeScreen( sharedTransitionScope = this@SharedTransitionLayout, animatedVisibilityScope = this@composable, onItemClick = { navController.navigate("details/$it") }) } composable( "details/{item}", arguments = listOf(navArgument("item") { type = NavType.IntType }) ) { val id = backStackEntry.arguments?.getInt("item") ?: 0 val snack = listSnacks[id] DetailsScreen( id = id, snack = snack, sharedTransitionScope = this@SharedTransitionLayout, animatedVisibilityScope = this@composable, onBackPressed = { navController.popBackStack() } ) } } } } SharedElementsWithNavigationSnippets.kt ``` -------------------------------- ### Animating Text Color Source: https://developer.android.com/develop/ui/compose/animation/quick-guide?hl=zh-CN Demonstrates how to animate the text color using `BasicText` and `rememberInfiniteTransition` with `animateColor`. ```kotlin val infiniteTransition = rememberInfiniteTransition(label = "infinite transition") val animatedColor by infiniteTransition.animateColor( initialValue = Color(0xFF60DDAD), targetValue = Color(0xFF4285F4), animationSpec = infiniteRepeatable(tween(1000), RepeatMode.Reverse), label = "color" ) BasicText( text = "Hello Compose", color = { animatedColor }, // ... ) AnimationQuickGuide.kt ``` -------------------------------- ### Navigation 3 Source: https://developer.android.com/develop/ui/compose/animation/shared-elements/navigation?hl=de Example of using shared element transitions with Navigation 3. ```kotlin @Composable fun SharedElement_Nav3() { SharedTransitionLayout { val backStack = rememberNavBackStack(HomeRoute) // Note: NavDisplay accepts a `sharedTransitionScope` parameter, which is used to animate // NavEntry instances between scenes. This parameter *isn't* required for shared element // or shared bounds transitioning elements between different NavEntry, as demonstrated in // this sample. // See https://developer.android.com/guide/navigation/navigation-3/animate-destinations#transition-nav-entries NavDisplay( modifier = Modifier.safeDrawingPadding(), backStack = backStack, entryProvider = entryProvider { entry { HomeScreen( sharedTransitionScope = this@SharedTransitionLayout, animatedVisibilityScope = LocalNavAnimatedContentScope.current, onItemClick = { backStack.add(DetailsRoute(it)) }) } entry { detailsRoute -> val id = detailsRoute.item val snack = listSnacks[id] DetailsScreen( id = id, snack = snack, sharedTransitionScope = this@SharedTransitionLayout, animatedVisibilityScope = LocalNavAnimatedContentScope.current, onBackPressed = { backStack.removeLastOrNull() }, ) } }) } } SharedElementsWithNavigationSnippets.kt ``` -------------------------------- ### Switching Between Content Types Source: https://developer.android.com/develop/ui/compose/animation/quick-guide?hl=zh-CN Shows how to use `AnimatedContent` for animating transitions between different composables, with `Crossfade` as an alternative for standard fade-in/out. ```kotlin var state by remember { mutableStateOf(UiState.Loading) } AnimatedContent( state, transitionSpec = { fadeIn( animationSpec = tween(3000) ) togetherWith fadeOut(animationSpec = tween(3000)) }, modifier = Modifier.clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) ) { targetState -> when (targetState) { UiState.Loading -> { LoadingScreen() } UiState.Loaded -> { LoadedScreen() } UiState.Error -> { ErrorScreen() } } } AnimationQuickGuide.kt ``` -------------------------------- ### Repeating Animations Source: https://developer.android.com/develop/ui/compose/animation/quick-guide?hl=zh-CN Explains how to use `rememberInfiniteTransition` with `infiniteRepeatable` `animationSpec` for continuous animation playback, and `RepeatModes` for playback direction. ```kotlin val infiniteTransition = rememberInfiniteTransition(label = "infinite") val color by infiniteTransition.animateColor( initialValue = Color.Green, targetValue = Color.Blue, animationSpec = infiniteRepeatable( animation = tween(1000, easing = LinearEasing), repeatMode = RepeatMode.Reverse ), label = "color" ) Column( modifier = Modifier.drawBehind { drawRect(color) } ) { // your composable here } AnimationQuickGuide.kt ``` -------------------------------- ### Shared Element Transition Example Source: https://developer.android.com/develop/ui/compose/animation/shared-elements This example demonstrates how to use Modifier.sharedBounds() for shared element transitions. The order of modifiers is crucial for a smooth animation. Placing padding after sharedBounds can lead to visual jumps if not handled carefully. ```kotlin var selectFirst by remember { mutableStateOf(true) } val key = remember { Any() } SharedTransitionLayout( Modifier .fillMaxSize() .padding(10.dp) .clickable { selectFirst = !selectFirst } ) { AnimatedContent(targetState = selectFirst, label = "AnimatedContent") { targetState -> if (targetState) { Box( Modifier .padding(12.dp) .sharedBounds( rememberSharedContentState(key = key), animatedVisibilityScope = this@AnimatedContent ) .border(2.dp, Color.Red) ) { Text( "Hello", fontSize = 20.sp ) } } else { Box( Modifier .offset(180.dp, 180.dp) .sharedBounds( rememberSharedContentState( key = key, ), animatedVisibilityScope = this@AnimatedContent ) .border(2.dp, Color.Red) // This padding is placed after sharedBounds, but it doesn't match the // other shared elements modifier order, resulting in visual jumps .padding(12.dp) ) { Text( "Hello", fontSize = 36.sp ) } } } } ``` -------------------------------- ### TargetBasedAnimation Example Source: https://developer.android.com/develop/ui/compose/animation/value-based?hl=de Manually controls the playback time of a TargetBasedAnimation based on frame time provided by withFrameNanos. ```kotlin val anim = remember { TargetBasedAnimation( animationSpec = tween(200), typeConverter = Float.VectorConverter, initialValue = 200f, targetValue = 1000f ) } var playTime by remember { mutableLongStateOf(0L) } LaunchedEffect(anim) { val startTime = withFrameNanos { it } do { playTime = withFrameNanos { it } - startTime val animationValue = anim.getValueFromNanos(playTime) } while (someCustomCondition()) ``` -------------------------------- ### Start animation immediately using MutableTransitionState Source: https://developer.android.com/develop/ui/compose/animation/value-based?hl=ko Use `MutableTransitionState` with `updateTransition` to set an initial state different from the first target state, allowing animations to start immediately. ```kotlin // Start in collapsed state and immediately animate to expanded var currentState = remember { MutableTransitionState(BoxState.Collapsed) } currentState.targetState = BoxState.Expanded val transition = rememberTransition(currentState, label = "box state") // …… ``` -------------------------------- ### Shared elements with AnimatedVisibility Source: https://developer.android.com/develop/ui/compose/animation/shared-elements?hl=ko Example demonstrating shared elements within an AnimatedVisibility composable, showing how to use the animatedVisibilityScope. ```kotlin var selectedSnack by remember { mutableStateOf(null) } SharedTransitionLayout(modifier = Modifier.fillMaxSize()) { LazyColumn( // ... ) { items(listSnacks) { snack -> AnimatedVisibility( visible = snack != selectedSnack, enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut(), modifier = Modifier.animateItem() ) { Box( modifier = Modifier .sharedBounds( sharedContentState = rememberSharedContentState(key = "${snack.name}-bounds"), // Using the scope provided by AnimatedVisibility animatedVisibilityScope = this, clipInOverlayDuringTransition = OverlayClip(shapeForSharedElement) ) .background(Color.White, shapeForSharedElement) .clip(shapeForSharedElement) ) { SnackContents( snack = snack, modifier = Modifier.sharedElement( sharedContentState = rememberSharedContentState(key = snack.name), animatedVisibilityScope = this@AnimatedVisibility ), onClick = { selectedSnack = snack } ) } } } } // Contains matching AnimatedContent with sharedBounds modifiers. SnackEditDetails( snack = selectedSnack, onConfirmClick = { selectedSnack = null } ) } AnimatedVisibilitySharedElementSnippets.kt ``` -------------------------------- ### Animating Navigation to Different Destinations Source: https://developer.android.com/develop/ui/compose/animation/quick-guide?hl=zh-CN Illustrates how to animate transitions between composables when using the `navigation-compose` artifact by specifying `enterTransition` and `exitTransition`. ```kotlin val navController = rememberNavController() NavHost( navController = navController, startDestination = "landing", enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None } ) { composable("landing") { ScreenLanding( // ... ) } composable( "detail/{photoUrl}", arguments = listOf(navArgument("photoUrl") { type = NavType.StringType }), enterTransition = { fadeIn( animationSpec = tween( 300, easing = LinearEasing ) ) + slideIntoContainer( animationSpec = tween(300, easing = EaseIn), towards = AnimatedContentTransitionScope.SlideDirection.Start ) }, exitTransition = { fadeOut( animationSpec = tween( 300, easing = LinearEasing ) ) + slideOutOfContainer( animationSpec = tween(300, easing = EaseOut), towards = AnimatedContentTransitionScope.SlideDirection.End ) } ) { ScreenDetails( // ... ) } } AnimationQuickGuide.kt ``` -------------------------------- ### Basic SharedTransitionLayout Setup Source: https://developer.android.com/develop/ui/compose/animation/shared-elements?hl=th Demonstrates how to wrap AnimatedContent with SharedTransitionLayout to enable shared element transitions. It shows how to pass the necessary scopes to child composables. ```kotlin var showDetails by remember { mutableStateOf(false) } SharedTransitionLayout { AnimatedContent( showDetails, label = "basic_transition" ) { targetState -> if (!targetState) { MainContent( onShowDetails = { showDetails = true }, animatedVisibilityScope = this@AnimatedContent, sharedTransitionScope = this@SharedTransitionLayout ) } else { DetailsContent( onBack = { showDetails = false }, animatedVisibilityScope = this@AnimatedContent, sharedTransitionScope = this@SharedTransitionLayout ) } } } BasicSharedElementSnippets.kt ``` -------------------------------- ### Parameter Scope Passing Source: https://developer.android.com/develop/ui/compose/animation/shared-elements?hl=ko Example of passing AnimatedVisibilityScope and SharedTransitionScope as parameters when the hierarchy is not deeply nested. ```kotlin @Composable fun MainContent( animatedVisibilityScope: AnimatedVisibilityScope, sharedTransitionScope: SharedTransitionScope ) { } @Composable fun Details( animatedVisibilityScope: AnimatedVisibilityScope, sharedTransitionScope: SharedTransitionScope ) { } BasicSharedElementSnippets.kt ``` -------------------------------- ### Gesture-based animation with Animatable Source: https://developer.android.com/develop/ui/compose/animation/advanced?hl=id This example demonstrates how to use `Animatable` to represent the offset of a component and handle tap events using `pointerInput`. When a new tap is detected, `animateTo` is called to animate the offset to the tap position. `animateTo` can interrupt ongoing animations. ```kotlin import androidx.compose.animation.core.Animatable import androidx.compose.foundation.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.offset import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.IntOffset import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import kotlin.math.roundToInt @Composable fun Gesture() { val offset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) } Box( modifier = Modifier .fillMaxSize() .pointerInput(Unit) { coroutineScope { while (true) { // Detect a tap event and obtain its position. awaitPointerEventScope { val position = awaitFirstDown().position launch { // Animate to the tap position. offset.animateTo(position) } } } } } ) { Circle(modifier = Modifier.offset { offset.value.toIntOffset() }) } } private fun Offset.toIntOffset() = IntOffset(x.roundToInt(), y.roundToInt()) ``` -------------------------------- ### Start animation with MutableTransitionState Source: https://developer.android.com/develop/ui/compose/animation/value-based?hl=de Using `MutableTransitionState` to define an initial state that differs from the first target state, allowing animations to start immediately. ```kotlin // Start in collapsed state and immediately animate to expanded var currentState = remember { MutableTransitionState(BoxState.Collapsed) } currentState.targetState = BoxState.Expanded val transition = rememberTransition(currentState, label = "box state") ``` -------------------------------- ### Shared Element Transition Example Source: https://developer.android.com/develop/ui/compose/animation/shared-elements?hl=de Demonstrates a shared element transition using Modifier.sharedBounds and AnimatedContent. Highlights the importance of modifier order. ```kotlin var selectFirst by remember { mutableStateOf(true) } val key = remember { Any() } SharedTransitionLayout( Modifier .fillMaxSize() .padding(10.dp) .clickable { selectFirst = !selectFirst } ) { AnimatedContent(targetState = selectFirst, label = "AnimatedContent") { if (targetState) { Box( Modifier .padding(12.dp) .sharedBounds( rememberSharedContentState(key = key), animatedVisibilityScope = this@AnimatedContent ) .border(2.dp, Color.Red) ) { Text( "Hello", fontSize = 20.sp ) } } else { Box( Modifier .offset(180.dp, 180.dp) .sharedBounds( rememberSharedContentState( key = key, ), animatedVisibilityScope = this@AnimatedContent ) .border(2.dp, Color.Red) // This padding is placed after sharedBounds, but it doesn't match the // other shared elements modifier order, resulting in visual jumps .padding(12.dp) ) { Text( "Hello", fontSize = 36.sp ) } } } } ``` -------------------------------- ### Shared elements with AnimatedVisibility Source: https://developer.android.com/develop/ui/compose/animation/shared-elements?hl=zh-CN Example demonstrating how to combine shared elements with AnimatedVisibility for a delayed grid effect. ```kotlin var selectedSnack by remember { mutableStateOf(null) } SharedTransitionLayout(modifier = Modifier.fillMaxSize()) { LazyColumn( // ... ) { items(listSnacks) { snack -> AnimatedVisibility( visible = snack != selectedSnack, enter = fadeIn() + scaleIn(), exit = fadeOut() + scaleOut(), modifier = Modifier.animateItem() ) { Box( modifier = Modifier .sharedBounds( sharedContentState = rememberSharedContentState(key = "${snack.name}-bounds"), // Using the scope provided by AnimatedVisibility animatedVisibilityScope = this, clipInOverlayDuringTransition = OverlayClip(shapeForSharedElement) ) .background(Color.White, shapeForSharedElement) .clip(shapeForSharedElement) ) { SnackContents( snack = snack, modifier = Modifier.sharedElement( sharedContentState = rememberSharedContentState(key = snack.name), animatedVisibilityScope = this@AnimatedVisibility ), onClick = { selectedSnack = snack } ) } } } } // Contains matching AnimatedContent with sharedBounds modifiers. SnackEditDetails( snack = selectedSnack, onConfirmClick = { selectedSnack = null } ) } ``` -------------------------------- ### Define states using an enum Source: https://developer.android.com/develop/ui/compose/animation/value-based?hl=de An example of defining states for a transition using a custom enum class for type safety. ```kotlin enum class BoxState { Collapsed, Expanded } ``` -------------------------------- ### Implementing swipe-to-dismiss with Animatable Source: https://developer.android.com/develop/ui/compose/animation/advanced?hl=th This example shows a common pattern of synchronizing animation values with touch events, specifically for a swipe-to-dismiss gesture implemented as a `Modifier`. It uses `Animatable` for horizontal offset, `stop` to interrupt ongoing animations on touch down, `snapTo` to update the value during drag, `VelocityTracker` for fling calculations, and `animateDecay` for the fling animation. It also demonstrates `updateBounds` to constrain the animation and `animateTo` to slide back to the original position. ```kotlin import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.splineBasedDecay import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.unit.size import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import androidx.compose.animation.core.animateDecay import androidx.compose.animation.core.updateBounds import androidx.compose.ui.geometry.Size import androidx.compose.ui.input.pointer.changedToDown import androidx.compose.ui.input.pointer.changedToUp import androidx.compose.ui.input.pointer.consumeAllChanges import androidx.compose.ui.input.pointer.positionChange import androidx.compose.ui.input.pointer.util.VelocityTracker import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.IntSize import kotlin.math.absoluteValue fun Modifier.swipeToDismiss( onDismissed: () -> Unit ): Modifier = composed { val offsetX = remember { Animatable(0f) } pointerInput(Unit) { // Used to calculate fling decay. val decay = splineBasedDecay(this) // Use suspend functions for touch events and the Animatable. coroutineScope { while (true) { val velocityTracker = VelocityTracker() // Stop any ongoing animation. offsetX.stop() awaitPointerEventScope { // Detect a touch down event. val pointerId = awaitFirstDown().id horizontalDrag(pointerId) { change -> // Update the animation value with touch events. launch { offsetX.snapTo( offsetX.value + change.positionChange().x ) } velocityTracker.addPosition( change.uptimeMillis, change.position ) } } // No longer receiving touch events. Prepare the animation. val velocity = velocityTracker.calculateVelocity().x val targetOffsetX = decay.calculateTargetValue( offsetX.value, velocity ) // The animation stops when it reaches the bounds. offsetX.updateBounds( lowerBound = -size.width.toFloat(), upperBound = size.width.toFloat() ) launch { if (targetOffsetX.absoluteValue <= size.width) { // Not enough velocity; Slide back. offsetX.animateTo( targetValue = 0f, initialVelocity = velocity ) } else { // The element was swiped away. offsetX.animateDecay(velocity, decay) onDismissed() } } } } } } ``` -------------------------------- ### Advanced Animation Snippet Source: https://developer.android.com/develop/ui/compose/animation/advanced An example demonstrating advanced animation techniques in Jetpack Compose, specifically related to swipe-to-dismiss gestures. ```kotlin // The element was swiped away. offsetX.animateDecay(velocity, decay) onDismissed() } } } } } } .offset { IntOffset(offsetX.value.roundToInt(), 0) } } AdvancedAnimationSnippets.kt ``` -------------------------------- ### Dynamically enabling shared element transitions Source: https://developer.android.com/develop/ui/compose/animation/shared-elements/customize?hl=zh-TW Example of dynamically enabling or disabling shared element transitions based on navigation state. ```kotlin SharedTransitionLayout { val transition = updateTransition(currentState) transition.AnimatedContent { targetState -> // Create the configuration that depends on state changing. fun animationConfig() : SharedTransitionScope.SharedContentConfig { return object : SharedTransitionScope.SharedContentConfig { override val SharedTransitionScope.SharedContentState.isEnabled: Boolean // For this example, we only enable the transition in one direction // from A -> B and not the other way around. get() = transition.currentState == "A" && transition.targetState == "B" } } when (targetState) { "A" -> Box( modifier = Modifier .sharedElement( rememberSharedContentState( key = "shared_box", config = animationConfig() ), animatedVisibilityScope = this ) // ... ) { // Your content } "B" -> { Box( modifier = Modifier .sharedElement( rememberSharedContentState( key = "shared_box", config = animationConfig() ), animatedVisibilityScope = this ) // ... ) { // Your content } } } } } ``` -------------------------------- ### Concurrent Animations with Coroutines Source: https://developer.android.com/develop/ui/compose/animation/quick-guide?hl=de Use coroutine APIs like `Animatable#animateTo()` within `launch` blocks to achieve concurrent animations. Multiple animations start simultaneously. ```kotlin val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { launch { alphaAnimation.animateTo(1f) } launch { yAnimation.animateTo(100f) } } ``` -------------------------------- ### Sequential animations Source: https://developer.android.com/develop/ui/compose/animation/quick-guide Use `Animatable` coroutine APIs to run sequential animations. Calling `animateTo` sequentially on an `Animatable` ensures each animation completes before the next one starts. ```kotlin val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { alphaAnimation.animateTo(1f) yAnimation.animateTo(100f) yAnimation.animateTo(500f, animationSpec = tween(100)) } AnimationQuickGuide.kt ``` -------------------------------- ### Gesture detection with Animatable Source: https://developer.android.com/develop/ui/compose/animation/advanced?hl=he This example demonstrates how to use `Animatable` to represent the offset of a circular element and handle tap events using `pointerInput`. When a tap occurs, `animateTo` is called to animate the offset to the tap position. If a tap happens during an ongoing animation, `animateTo` interrupts the current animation and starts a new one. ```kotlin @Composable fun Gesture() { val offset = remember { Animatable(Offset(0f, 0f), Offset.VectorConverter) } Box( modifier = Modifier .fillMaxSize() .pointerInput(Unit) { coroutineScope { while (true) { // Detect a tap event and obtain its position. awaitPointerEventScope { val position = awaitFirstDown().position launch { // Animate to the tap position. offset.animateTo(position) } } } } } ) { Circle(modifier = Modifier.offset { offset.value.toIntOffset() }) } } private fun Offset.toIntOffset() = IntOffset(x.roundToInt(), y.roundToInt()) AdvancedAnimationSnippets.kt ``` -------------------------------- ### Concurrent animations with `updateTransition` Source: https://developer.android.com/develop/ui/compose/animation/quick-guide The `updateTransition` API allows controlling multiple attribute animations concurrently using the same state. This example animates `rect` and `borderWidth` based on a state change. ```kotlin var currentState by remember { mutableStateOf(BoxState.Collapsed) } val transition = updateTransition(currentState, label = "transition") val rect by transition.animateRect(label = "rect") { state -> when (state) { BoxState.Collapsed -> Rect(0f, 0f, 100f, 100f) BoxState.Expanded -> Rect(100f, 100f, 300f, 300f) } } val borderWidth by transition.animateDp(label = "borderWidth") { state -> when (state) { BoxState.Collapsed -> 1.dp BoxState.Expanded -> 0.dp } } AnimationQuickGuide.kt ``` -------------------------------- ### Concurrent animations Source: https://developer.android.com/develop/ui/compose/animation/quick-guide Use coroutine APIs (`Animatable#animateTo()` or `animate`) or the `Transition` API to achieve concurrent animations. When multiple launch functions are used within a coroutine scope, animations start simultaneously. ```kotlin val alphaAnimation = remember { Animatable(0f) } val yAnimation = remember { Animatable(0f) } LaunchedEffect("animationKey") { launch { alphaAnimation.animateTo(1f) } launch { yAnimation.animateTo(100f) } } AnimationQuickGuide.kt ``` -------------------------------- ### Using transitions with AnimatedVisibility and AnimatedContent Source: https://developer.android.com/develop/ui/compose/animation/value-based?hl=id This example demonstrates how to use `AnimatedVisibility` and `AnimatedContent` as extension functions of `Transition` to animate UI changes based on state. ```kotlin var selected by remember { mutableStateOf(false) } // Animates changes when `selected` is changed. val transition = updateTransition(selected, label = "selected state") val borderColor by transition.animateColor(label = "border color") { isSelected -> if (isSelected) Color.Magenta else Color.White } val elevation by transition.animateDp(label = "elevation") { isSelected -> if (isSelected) 10.dp else 2.dp } Surface( onClick = { selected = !selected }, shape = RoundedCornerShape(8.dp), border = BorderStroke(2.dp, borderColor), shadowElevation = elevation ) { Column( modifier = Modifier .fillMaxWidth() .padding(16.dp) ) { Text(text = "Hello, world!") // AnimatedVisibility as a part of the transition. transition.AnimatedVisibility( visible = { targetSelected -> targetSelected }, enter = expandVertically(), exit = shrinkVertically() ) { Text(text = "It is fine today.") } // AnimatedContent as a part of the transition. transition.AnimatedContent { targetState -> if (targetState) { Text(text = "Selected") } else { Icon(imageVector = Icons.Default.Phone, contentDescription = "Phone") } } } } AnimationSnippets.kt ``` -------------------------------- ### Clipping a shared element Source: https://developer.android.com/develop/ui/compose/animation/shared-elements/customize?hl=de Example of clipping a shared element using Modifier.clip() after Modifier.sharedElement(). ```kotlin Image( painter = painterResource(id = R.drawable.cupcake), contentDescription = "Cupcake", modifier = Modifier .size(100.dp) .sharedElement( rememberSharedContentState(key = "image"), animatedVisibilityScope = this@AnimatedContent ) .clip(RoundedCornerShape(16.dp)), contentScale = ContentScale.Crop ) ``` -------------------------------- ### Animating Elevation Source: https://developer.android.com/develop/ui/compose/animation/quick-guide Animates the elevation of a Composable using `animateDpAsState` and `Modifier.graphicsLayer{}`. ```kotlin val mutableInteractionSource = remember { MutableInteractionSource() } val pressed = mutableInteractionSource.collectIsPressedAsState() val elevation = animateDpAsState( targetValue = if (pressed.value) { 32.dp } else { 8.dp }, label = "elevation" ) Box( modifier = Modifier .size(100.dp) .align(Alignment.Center) .graphicsLayer { this.shadowElevation = elevation.value.toPx() } .clickable(interactionSource = mutableInteractionSource, indication = null) { } .background(colorGreen) ) { } ``` -------------------------------- ### Animating Padding Source: https://developer.android.com/develop/ui/compose/animation/quick-guide Animates the padding of a Composable using `animateDpAsState` and `Modifier.padding()`. ```kotlin var toggled by remember { mutableStateOf(false) } val animatedPadding by animateDpAsState( if (toggled) { 0.dp } else { 20.dp }, label = "padding" ) Box( modifier = Modifier .aspectRatio(1f) .fillMaxSize() .padding(animatedPadding) .background(Color(0xff53D9A1)) .clickable( interactionSource = remember { MutableInteractionSource() }, indication = null ) { toggled = !toggled } ) ``` -------------------------------- ### Managing scopes using CompositionLocals Source: https://developer.android.com/develop/ui/compose/animation/shared-elements?hl=zh-CN An example of how to use composition locals to pass around the shared transition scope and animated visibility scope, far down your UI tree. ```kotlin BasicSharedElementSnippets.kt val LocalNavAnimatedVisibilityScope = compositionLocalOf { null } val LocalSharedTransitionScope = compositionLocalOf { null } @Composable private fun SharedElementScope_CompositionLocal() { // An example of how to use composition locals to pass around the shared transition scope, far down your UI tree. // ... SharedTransitionLayout { CompositionLocalProvider( LocalSharedTransitionScope provides this ) { // This could also be your top-level NavHost as this provides an AnimatedContentScope AnimatedContent(state, label = "Top level AnimatedContent") { targetState -> CompositionLocalProvider(LocalNavAnimatedVisibilityScope provides this) { // Now we can access the scopes in any nested composables as follows: val sharedTransitionScope = LocalSharedTransitionScope.current ?: throw IllegalStateException("No SharedElementScope found") val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current ?: throw IllegalStateException("No AnimatedVisibility found") } // ... } } } } BasicSharedElementSnippets.kt ``` -------------------------------- ### Creating child transitions for complex components Source: https://developer.android.com/develop/ui/compose/animation/value-based?hl=pl Demonstrates how to use `createChildTransition` to create child transitions for complex components, allowing for separation of concerns and animation management. ```kotlin enum class DialerState { DialerMinimized, NumberPad } @Composable fun DialerButton(isVisibleTransition: Transition) { // `isVisibleTransition` spares the need for the content to know // about other DialerStates. Instead, the content can focus on // animating the state change between visible and not visible. } @Composable fun NumberPad(isVisibleTransition: Transition) { // `isVisibleTransition` spares the need for the content to know // about other DialerStates. Instead, the content can focus on // animating the state change between visible and not visible. } @Composable fun Dialer(dialerState: DialerState) { val transition = updateTransition(dialerState, label = "dialer state") Box { // Creates separate child transitions of Boolean type for NumberPad // and DialerButton for any content animation between visible and // not visible NumberPad( transition.createChildTransition { it == DialerState.NumberPad } ) DialerButton( transition.createChildTransition { it == DialerState.DialerMinimized } ) } } AnimationSnippets.kt ``` -------------------------------- ### Swipe-to-dismiss modifier with Animatable Source: https://developer.android.com/develop/ui/compose/animation/advanced?hl=id This example shows a "swipe to dismiss" pattern implemented as a `Modifier`. The horizontal offset of the element is represented by an `Animatable`. Touch events are used to update the `Animatable`'s value via `snapTo`, and `VelocityTracker` is used to calculate fling velocity for `animateDecay`. The animation can be stopped with `stop` and animated back to the original position with `animateTo`. ```kotlin import androidx.compose.animation.core.Animatable import androidx.compose.animation.core.animateDecay import androidx.compose.animation.core.splineBasedDecay import androidx.compose.animation.core.updateBounds import androidx.compose.foundation.gestures.awaitFirstDown import androidx.compose.foundation.gestures.horizontalDrag import androidx.compose.runtime.remember import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.geometry.Size import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Velocity import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.toSize import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.launch import kotlin.math.absoluteValue fun Modifier.swipeToDismiss( onDismissed: () -> Unit ): Modifier = composed { val offsetX = remember { Animatable(0f) } val density = LocalDensity.current val layoutDirection = LocalLayoutDirection.current pointerInput(Unit) { // Used to calculate fling decay. val decay = splineBasedDecay(this) // Use suspend functions for touch events and the Animatable. coroutineScope { while (true) { val velocityTracker = VelocityTracker() // Stop any ongoing animation. offsetX.stop() awaitPointerEventScope { // Detect a touch down event. val pointerId = awaitFirstDown().id horizontalDrag(pointerId) { change -> // Update the animation value with touch events. launch { offsetX.snapTo( offsetX.value + change.positionChange().x ) } velocityTracker.addPosition( change.uptimeMillis, change.position ) } } // No longer receiving touch events. Prepare the animation. val velocity = velocityTracker.calculateVelocity().x val targetOffsetX = decay.calculateTargetValue( offsetX.value, velocity ) // The animation stops when it reaches the bounds. val size = with(density) { Size(size.width.toPx(), size.height.toPx()) } // Assuming size is accessible offsetX.updateBounds( lowerBound = -size.width, upperBound = size.width ) launch { if (targetOffsetX.absoluteValue <= size.width) { // Not enough velocity; Slide back. offsetX.animateTo( targetValue = 0f, initialVelocity = velocity ) } else { // Enough velocity to dismiss. // This part is incomplete in the original snippet, // but would typically involve animating off-screen and calling onDismissed. } } } } } } ``` -------------------------------- ### Modifier order for shared elements Source: https://developer.android.com/develop/ui/compose/animation/shared-elements?hl=ko Example illustrating the importance of modifier order with shared elements, showing how incorrect placement of padding can lead to visual jumps. ```kotlin var selectFirst by remember { mutableStateOf(true) } val key = remember { Any() } SharedTransitionLayout( Modifier .fillMaxSize() .padding(10.dp) .clickable { selectFirst = !selectFirst } ) { AnimatedContent(targetState = selectFirst, label = "AnimatedContent") { targetState -> if (targetState) { Box( Modifier .padding(12.dp) .sharedBounds( rememberSharedContentState(key = key), animatedVisibilityScope = this@AnimatedContent ) .border(2.dp, Color.Red) ) { Text( "Hello", fontSize = 20.sp ) } } else { Box( Modifier .offset(180.dp, 180.dp) .sharedBounds( rememberSharedContentState( key = key, ), animatedVisibilityScope = this@AnimatedContent ) .border(2.dp, Color.Red) // This padding is placed after sharedBounds, but it doesn't match the // other shared elements modifier order, resulting in visual jumps .padding(12.dp) ) { Text( "Hello", fontSize = 36.sp ) } } } } BasicSharedElementSnippets.kt ``` -------------------------------- ### Animatable example Source: https://developer.android.com/develop/ui/compose/animation/value-based?hl=es-419 Animates a color value between gray, green, and red based on a boolean state. ```kotlin // Start out gray and animate to green/red based on `ok` val color = remember { Animatable(Color.Gray) } LaunchedEffect(ok) { color.animateTo(if (ok) Color.Green else Color.Red) } Box( Modifier .fillMaxSize() .background(color.value) ) ``` -------------------------------- ### Basic Shared Element Transition Setup Source: https://developer.android.com/develop/ui/compose/animation/shared-elements?hl=zh-CN This snippet demonstrates how to wrap an AnimatedContent composable with SharedTransitionLayout and pass the necessary scopes to child composables to enable shared element transitions. ```kotlin var showDetails by remember { mutableStateOf(false) } SharedTransitionLayout { AnimatedContent( showDetails, label = "basic_transition" ) { targetState -> if (!targetState) { MainContent( onShowDetails = { showDetails = true }, animatedVisibilityScope = this@AnimatedContent, sharedTransitionScope = this@SharedTransitionLayout ) } else { DetailsContent( onBack = { showDetails = false }, animatedVisibilityScope = this@AnimatedContent, sharedTransitionScope = this@SharedTransitionLayout ) } } } ``` -------------------------------- ### Unique Keys for Shared Elements Source: https://developer.android.com/develop/ui/compose/animation/shared-elements?hl=th Illustrates how to create unique keys for shared elements using data classes, which is a recommended practice for complex shared element scenarios to avoid matching errors. ```kotlin data class SnackSharedElementKey( val snackId: Long, val origin: String, val type: SnackSharedElementType ) enum class SnackSharedElementType { Bounds, Image, Title, Tagline, Background } @Composable fun SharedElementUniqueKey() { // ... Box( modifier = Modifier .sharedElement( rememberSharedContentState( key = SnackSharedElementKey( snackId = 1, origin = "latest", type = SnackSharedElementType.Image ) ), animatedVisibilityScope = this@AnimatedVisibility ) ) // ... } ``` -------------------------------- ### SharedBounds Snippets Source: https://developer.android.com/develop/ui/compose/animation/shared-elements?hl=ko Example of using Modifier.sharedBounds() in Row and Column composables for shared element transitions. ```kotlin @Composable private fun MainContent( onShowDetails: () -> Unit, modifier: Modifier = Modifier, sharedTransitionScope: SharedTransitionScope, animatedVisibilityScope: AnimatedVisibilityScope ) { with(sharedTransitionScope) { Row( modifier = Modifier .padding(8.dp) .sharedBounds( rememberSharedContentState(key = "bounds"), animatedVisibilityScope = animatedVisibilityScope, enter = fadeIn(), exit = fadeOut(), resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds() ) // ... ) { // ... } } } @Composable private fun DetailsContent( modifier: Modifier = Modifier, onBack: () -> Unit, sharedTransitionScope: SharedTransitionScope, animatedVisibilityScope: AnimatedVisibilityScope ) { with(sharedTransitionScope) { Column( modifier = Modifier .padding(top = 200.dp, start = 16.dp, end = 16.dp) .sharedBounds( rememberSharedContentState(key = "bounds"), animatedVisibilityScope = animatedVisibilityScope, enter = fadeIn(), exit = fadeOut(), resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds() ) // ... ) { // ... } } } SharedBoundsSnippets.kt ```