diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/BasicSharedElementSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/BasicSharedElementSnippets.kt index 74f388b7..d2fb25e1 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/BasicSharedElementSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/BasicSharedElementSnippets.kt @@ -55,93 +55,6 @@ import com.example.compose.snippets.R import com.example.compose.snippets.ui.theme.LavenderLight import com.example.compose.snippets.ui.theme.RoseLight -private class SharedElementBasicUsage1 { - @Preview - // [START android_compose_animations_shared_element_start] - @Composable - private fun SharedElementApp() { - var showDetails by remember { - mutableStateOf(false) - } - AnimatedContent( - showDetails, - label = "basic_transition" - ) { targetState -> - if (!targetState) { - MainContent(onShowDetails = { - showDetails = true - }) - } else { - DetailsContent(onBack = { - showDetails = false - }) - } - } - } - - @Composable - private fun MainContent(onShowDetails: () -> Unit) { - Row( - // [START_EXCLUDE] - modifier = Modifier - .padding(8.dp) - .border(1.dp, Color.Gray.copy(alpha = 0.5f), RoundedCornerShape(8.dp)) - .background(LavenderLight, RoundedCornerShape(8.dp)) - .clickable { - onShowDetails() - } - .padding(8.dp) - // [END_EXCLUDE] - ) { - Image( - painter = painterResource(id = R.drawable.cupcake), - contentDescription = "Cupcake", - modifier = Modifier - .size(100.dp) - .clip(CircleShape), - contentScale = ContentScale.Crop - ) - Text("Cupcake", fontSize = 21.sp) - } - } - - @Composable - private fun DetailsContent(modifier: Modifier = Modifier, onBack: () -> Unit) { - Column( - // [START_EXCLUDE] - modifier = Modifier - .padding(top = 200.dp, start = 16.dp, end = 16.dp) - .border(1.dp, Color.Gray.copy(alpha = 0.5f), RoundedCornerShape(8.dp)) - .background(RoseLight, RoundedCornerShape(8.dp)) - .clickable { - onBack() - } - .padding(8.dp) - // [END_EXCLUDE] - ) { - Image( - painter = painterResource(id = R.drawable.cupcake), - contentDescription = "Cupcake", - modifier = Modifier - .size(200.dp) - .clip(CircleShape), - contentScale = ContentScale.Crop - ) - Text("Cupcake", fontSize = 28.sp) - // [START_EXCLUDE] - Text( - "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet lobortis velit. " + - "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + - " Curabitur sagittis, lectus posuere imperdiet facilisis, nibh massa " + - "molestie est, quis dapibus orci ligula non magna. Pellentesque rhoncus " + - "hendrerit massa quis ultricies. Curabitur congue ullamcorper leo, at maximus" - ) - // [END_EXCLUDE] - } - } - // [END android_compose_animations_shared_element_start] -} - private class SharedElementBasicUsage2 { @Preview @Composable @@ -203,7 +116,9 @@ private class SharedElementBasicUsage2 { .clip(CircleShape), contentScale = ContentScale.Crop ) + // [START_EXCLUDE] Text("Cupcake", fontSize = 21.sp) + // [END_EXCLUDE] } } @@ -234,8 +149,8 @@ private class SharedElementBasicUsage2 { .clip(CircleShape), contentScale = ContentScale.Crop ) - Text("Cupcake", fontSize = 28.sp) // [START_EXCLUDE] + Text("Cupcake", fontSize = 28.sp) Text( "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet lobortis velit. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + @@ -315,6 +230,7 @@ private class SharedElementBasicUsage3 { .clip(CircleShape), contentScale = ContentScale.Crop ) + // [START_EXCLUDE] Text( "Cupcake", fontSize = 21.sp, modifier = Modifier.sharedBounds( @@ -322,6 +238,7 @@ private class SharedElementBasicUsage3 { animatedVisibilityScope = animatedVisibilityScope ) ) + // [END_EXCLUDE] } } } @@ -358,6 +275,7 @@ private class SharedElementBasicUsage3 { .clip(CircleShape), contentScale = ContentScale.Crop ) + // [START_EXCLUDE] Text( "Cupcake", fontSize = 28.sp, modifier = Modifier.sharedBounds( @@ -365,7 +283,6 @@ private class SharedElementBasicUsage3 { animatedVisibilityScope = animatedVisibilityScope ) ) - // [START_EXCLUDE] Text( "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet lobortis velit. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/CustomizeSharedElementsSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/CustomizeSharedElementsSnippets.kt index 4e38c18b..cf0a9c01 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/CustomizeSharedElementsSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/CustomizeSharedElementsSnippets.kt @@ -27,12 +27,15 @@ import androidx.compose.animation.ExitTransition import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionLayout import androidx.compose.animation.SharedTransitionScope -import androidx.compose.animation.core.Spring +import androidx.compose.animation.core.ArcMode +import androidx.compose.animation.core.ExperimentalAnimationSpecApi +import androidx.compose.animation.core.FastOutSlowInEasing import androidx.compose.animation.core.keyframes -import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically import androidx.compose.foundation.Image import androidx.compose.foundation.background import androidx.compose.foundation.border @@ -83,66 +86,79 @@ import com.example.compose.snippets.R import com.example.compose.snippets.ui.theme.LavenderLight import com.example.compose.snippets.ui.theme.RoseLight -private class SharedElementBoundsTransform { - @Preview - @Composable - private fun SharedElementApp() { - 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 - ) - } +@Preview +@Composable +fun SharedElementApp_BoundsTransformExample() { + 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 + ) } } } +} - @Composable - private fun MainContent( - onShowDetails: () -> Unit, - modifier: Modifier = Modifier, - sharedTransitionScope: SharedTransitionScope, - animatedVisibilityScope: AnimatedVisibilityScope - ) { - with(sharedTransitionScope) { +@OptIn(ExperimentalAnimationSpecApi::class) +@Composable +private fun MainContent( + onShowDetails: () -> Unit, + modifier: Modifier = Modifier, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + with(sharedTransitionScope) { + Box(modifier = Modifier.fillMaxSize()) { Row( modifier = Modifier .padding(8.dp) + .fillMaxWidth() .sharedBounds( rememberSharedContentState(key = "bounds"), animatedVisibilityScope = animatedVisibilityScope, - enter = fadeIn(), - exit = fadeOut() + enter = fadeIn( + tween( + boundsAnimationDurationMillis, + easing = FastOutSlowInEasing + ) + ), + exit = fadeOut( + tween( + boundsAnimationDurationMillis, + easing = FastOutSlowInEasing + ) + ), + boundsTransform = boundsTransform ) - // [START_EXCLUDE] .border(1.dp, Color.Gray.copy(alpha = 0.5f), RoundedCornerShape(8.dp)) .background(LavenderLight, RoundedCornerShape(8.dp)) - .clickable { + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { onShowDetails() } .padding(8.dp) - // [END_EXCLUDE] ) { - // [START_EXCLUDE] Image( painter = painterResource(id = R.drawable.cupcake), contentDescription = "Cupcake", @@ -150,69 +166,72 @@ private class SharedElementBoundsTransform { .sharedElement( rememberSharedContentState(key = "image"), animatedVisibilityScope = animatedVisibilityScope, - boundsTransform = { _, _ -> - spring( - stiffness = Spring.StiffnessMediumLow, - dampingRatio = Spring.DampingRatioMediumBouncy - ) - } + boundsTransform = boundsTransform ) .size(100.dp) .clip(CircleShape), contentScale = ContentScale.Crop ) + val textBoundsTransform = BoundsTransform { initialBounds, targetBounds -> + keyframes { + durationMillis = boundsAnimationDurationMillis + initialBounds at 0 using ArcMode.ArcBelow using FastOutSlowInEasing + targetBounds at boundsAnimationDurationMillis + } + } Text( "Cupcake", fontSize = 21.sp, modifier = Modifier.sharedBounds( rememberSharedContentState(key = "title"), - animatedVisibilityScope = animatedVisibilityScope + animatedVisibilityScope = animatedVisibilityScope, + boundsTransform = textBoundsTransform ) ) - // [END_EXCLUDE] } } } +} - @Composable - private fun DetailsContent( - modifier: Modifier = Modifier, - onBack: () -> Unit, - sharedTransitionScope: SharedTransitionScope, - animatedVisibilityScope: AnimatedVisibilityScope - ) { - with(sharedTransitionScope) { +@OptIn(ExperimentalAnimationSpecApi::class) +@Composable +private fun DetailsContent( + modifier: Modifier = Modifier, + onBack: () -> Unit, + sharedTransitionScope: SharedTransitionScope, + animatedVisibilityScope: AnimatedVisibilityScope +) { + with(sharedTransitionScope) { + Box(modifier = Modifier.fillMaxSize()) { Column( modifier = Modifier + .padding(top = 200.dp, start = 16.dp, end = 16.dp) .sharedBounds( rememberSharedContentState(key = "bounds"), animatedVisibilityScope = animatedVisibilityScope, - enter = fadeIn(), - exit = fadeOut() + enter = fadeIn( + tween( + durationMillis = boundsAnimationDurationMillis, + easing = FastOutSlowInEasing + ) + ), + exit = fadeOut( + tween( + durationMillis = boundsAnimationDurationMillis, + easing = FastOutSlowInEasing + ) + ), + boundsTransform = boundsTransform ) - // [START_EXCLUDE] - .padding(top = 200.dp, start = 16.dp, end = 16.dp) .border(1.dp, Color.Gray.copy(alpha = 0.5f), RoundedCornerShape(8.dp)) .background(RoseLight, RoundedCornerShape(8.dp)) - .clickable { + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null + ) { onBack() } .padding(8.dp) - // [END_EXCLUDE] - ) { - // [START android_compose_shared_element_image_bounds_transform] - val imageBoundsTransform = BoundsTransform { initial, target -> - keyframes { - durationMillis = 500 - initial at 0 - Rect( - target.left + 100, - target.top, - target.right + 100, - target.bottom - ) at 300 - } - } Image( painter = painterResource(id = R.drawable.cupcake), contentDescription = "Cupcake", @@ -220,32 +239,47 @@ private class SharedElementBoundsTransform { .sharedElement( rememberSharedContentState(key = "image"), animatedVisibilityScope = animatedVisibilityScope, - boundsTransform = imageBoundsTransform + boundsTransform = boundsTransform ) .size(200.dp) .clip(CircleShape), contentScale = ContentScale.Crop ) - // [END android_compose_shared_element_image_bounds_transform] + // [START android_compose_shared_element_text_bounds_transform] + val textBoundsTransform = BoundsTransform { initialBounds, targetBounds -> + keyframes { + durationMillis = boundsAnimationDurationMillis + initialBounds at 0 using ArcMode.ArcBelow using FastOutSlowInEasing + targetBounds at boundsAnimationDurationMillis + } + } Text( "Cupcake", fontSize = 28.sp, modifier = Modifier.sharedBounds( rememberSharedContentState(key = "title"), - animatedVisibilityScope = animatedVisibilityScope + animatedVisibilityScope = animatedVisibilityScope, + boundsTransform = textBoundsTransform ) ) + // [END android_compose_shared_element_text_bounds_transform] Text( "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur sit amet lobortis velit. " + "Lorem ipsum dolor sit amet, consectetur adipiscing elit." + " Curabitur sagittis, lectus posuere imperdiet facilisis, nibh massa " + "molestie est, quis dapibus orci ligula non magna. Pellentesque rhoncus " + - "hendrerit massa quis ultricies. Curabitur congue ullamcorper leo, at maximus" + "hendrerit massa quis ultricies. Curabitur congue ullamcorper leo, at maximus", + modifier = Modifier.skipToLookaheadSize() ) } } } } +private val boundsTransform = BoundsTransform { _: Rect, _: Rect -> + tween(durationMillis = boundsAnimationDurationMillis, easing = FastOutSlowInEasing) +} +private const val boundsAnimationDurationMillis = 500 + @Preview @Composable private fun SharedElement_Clipping() { @@ -339,6 +373,34 @@ private fun SharedElement_Clipping() { } } +@Composable +private fun JetsnackBottomBar(modifier: Modifier) { +} + +@Composable +private fun EnterExitJetsnack() { + SharedTransitionLayout { + AnimatedVisibility(visible = true) { + // [START android_compose_shared_element_enter_exit] + JetsnackBottomBar( + modifier = Modifier + .renderInSharedTransitionScopeOverlay( + zIndexInOverlay = 1f, + ) + .animateEnterExit( + enter = fadeIn() + slideInVertically { + it + }, + exit = fadeOut() + slideOutVertically { + it + } + ) + ) + // [END android_compose_shared_element_enter_exit] + } + } +} + @Preview @Composable private fun SharedElement_SkipLookaheadSize() { diff --git a/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/SharedElementsWithNavigationSnippets.kt b/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/SharedElementsWithNavigationSnippets.kt index 56333e83..4bdb0916 100644 --- a/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/SharedElementsWithNavigationSnippets.kt +++ b/compose/snippets/src/main/java/com/example/compose/snippets/animations/sharedelement/SharedElementsWithNavigationSnippets.kt @@ -19,9 +19,10 @@ package com.example.compose.snippets.animations.sharedelement import androidx.annotation.DrawableRes +import androidx.compose.animation.AnimatedContentScope import androidx.compose.animation.ExperimentalSharedTransitionApi import androidx.compose.animation.SharedTransitionLayout -import androidx.compose.animation.core.tween +import androidx.compose.animation.SharedTransitionScope import androidx.compose.foundation.Image import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement @@ -40,12 +41,12 @@ import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.geometry.Rect import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.res.painterResource import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.navigation.NavHostController import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -67,54 +68,17 @@ val listSnacks = listOf( fun SharedElement_PredictiveBack() { // [START android_compose_shared_element_predictive_back] SharedTransitionLayout { - val boundsTransform = { _: Rect, _: Rect -> tween(1400) } - val navController = rememberNavController() NavHost( navController = navController, startDestination = "home" ) { - composable("home") { - LazyColumn( - modifier = Modifier - .fillMaxSize() - .padding(8.dp), - verticalArrangement = Arrangement.spacedBy(8.dp) - ) { - itemsIndexed(listSnacks) { index, item -> - Row( - Modifier.clickable { - navController.navigate("details/$index") - } - ) { - Spacer(modifier = Modifier.width(8.dp)) - Image( - painterResource(id = item.image), - contentDescription = item.description, - contentScale = ContentScale.Crop, - modifier = Modifier - .sharedElement( - rememberSharedContentState(key = "image-$index"), - animatedVisibilityScope = this@composable, - boundsTransform = boundsTransform - ) - .size(100.dp) - ) - Spacer(modifier = Modifier.width(8.dp)) - Text( - item.name, fontSize = 18.sp, - modifier = Modifier - .align(Alignment.CenterVertically) - .sharedElement( - rememberSharedContentState(key = "text-$index"), - animatedVisibilityScope = this@composable, - boundsTransform = boundsTransform - ) - ) - } - } - } + HomeScreen( + navController, + this@SharedTransitionLayout, + this@composable + ) } composable( "details/{item}", @@ -122,42 +86,106 @@ fun SharedElement_PredictiveBack() { ) { backStackEntry -> val id = backStackEntry.arguments?.getInt("item") val snack = listSnacks[id!!] - Column( - Modifier - .fillMaxSize() - .clickable { - navController.navigate("home") - } - ) { + DetailsScreen( + navController, + id, + snack, + this@SharedTransitionLayout, + this@composable + ) + } + } + } + // [END android_compose_shared_element_predictive_back] +} + +@Composable +private fun DetailsScreen( + navController: NavHostController, + id: Int, + snack: Snack, + sharedTransitionScope: SharedTransitionScope, + animatedContentScope: AnimatedContentScope +) { + with(sharedTransitionScope) { + Column( + Modifier + .fillMaxSize() + .clickable { + navController.navigate("home") + } + ) { + Image( + painterResource(id = snack.image), + contentDescription = snack.description, + contentScale = ContentScale.Crop, + modifier = Modifier.Companion + .sharedElement( + sharedTransitionScope.rememberSharedContentState(key = "image-$id"), + animatedVisibilityScope = animatedContentScope + ) + .aspectRatio(1f) + .fillMaxWidth() + ) + Text( + snack.name, fontSize = 18.sp, + modifier = + Modifier.Companion + .sharedElement( + sharedTransitionScope.rememberSharedContentState(key = "text-$id"), + animatedVisibilityScope = animatedContentScope + ) + .fillMaxWidth() + ) + } + } +} + +@Composable +private fun HomeScreen( + navController: NavHostController, + sharedTransitionScope: SharedTransitionScope, + animatedContentScope: AnimatedContentScope +) { + LazyColumn( + modifier = Modifier + .fillMaxSize() + .padding(8.dp), + verticalArrangement = Arrangement.spacedBy(8.dp) + ) { + itemsIndexed(listSnacks) { index, item -> + Row( + Modifier.clickable { + navController.navigate("details/$index") + } + ) { + Spacer(modifier = Modifier.width(8.dp)) + with(sharedTransitionScope) { Image( - painterResource(id = snack.image), - contentDescription = snack.description, + painterResource(id = item.image), + contentDescription = item.description, contentScale = ContentScale.Crop, - modifier = Modifier + modifier = Modifier.Companion .sharedElement( - rememberSharedContentState(key = "image-$id"), - animatedVisibilityScope = this@composable, - boundsTransform = boundsTransform + sharedTransitionScope.rememberSharedContentState(key = "image-$index"), + animatedVisibilityScope = animatedContentScope ) - .aspectRatio(1f) - .fillMaxWidth() + .size(100.dp) ) + Spacer(modifier = Modifier.width(8.dp)) Text( - snack.name, fontSize = 18.sp, - modifier = - Modifier + item.name, fontSize = 18.sp, + modifier = Modifier + .align(Alignment.CenterVertically) .sharedElement( - rememberSharedContentState(key = "text-$id"), - animatedVisibilityScope = this@composable, - boundsTransform = boundsTransform + sharedTransitionScope.rememberSharedContentState(key = "text-$index"), + animatedVisibilityScope = animatedContentScope, ) - .fillMaxWidth() ) } } } } -// [END android_compose_shared_element_predictive_back] } data class Snack(