From 41b6a6443fc3807a9311327b3bef0f3a039d7339 Mon Sep 17 00:00:00 2001 From: Rebecca Franks Date: Tue, 10 Dec 2024 20:16:31 +0000 Subject: [PATCH] Add an example of Shared elements that are seekable using SeekableTransitionState (#416) * Added seekable predictive back (redoing commit) * Added seekable predictive back (redoing commit) * spotless * Delete SeekableSharedElement.kt * Apply Spotless * Add enableOnBackInvokedCallback * Add comments and fix custom seeking predictive back example. * Apply Spotless --------- Co-authored-by: riggaroo --- compose/snippets/src/main/AndroidManifest.xml | 1 + .../CustomizeSharedElementsSnippets.kt | 101 ++++++++++++++++++ .../SharedElementsWithNavigationSnippets.kt | 27 ++--- 3 files changed, 116 insertions(+), 13 deletions(-) diff --git a/compose/snippets/src/main/AndroidManifest.xml b/compose/snippets/src/main/AndroidManifest.xml index c9b15e6c..4ec4d9b7 100644 --- a/compose/snippets/src/main/AndroidManifest.xml +++ b/compose/snippets/src/main/AndroidManifest.xml @@ -27,6 +27,7 @@ android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true" + android:enableOnBackInvokedCallback="true" android:theme="@style/Theme.Snippets"> (Screen.Home) + } + val transition = rememberTransition(transitionState = seekableTransitionState) + + PredictiveBackHandler(seekableTransitionState.currentState is Screen.Details) { progress -> + try { + // Whilst a back gesture is in progress, backEvents will be fired for each progress + // update. + progress.collect { backEvent -> + // For each backEvent that comes in, we manually seekTo the reported back progress + try { + seekableTransitionState.seekTo(backEvent.progress, targetState = Screen.Home) + } catch (e: CancellationException) { + // seekTo may be cancelled as expected, if animateTo or subsequent seekTo calls + // before the current seekTo finishes, in this case, we ignore the cancellation. + } + } + // Once collection has completed, we are either fully in the target state, or need + // to progress towards the end. + seekableTransitionState.animateTo(seekableTransitionState.targetState) + } catch (e: CancellationException) { + // When the predictive back gesture is cancelled, we snap to the end state to ensure + // it completes its seeking animation back to the currentState + seekableTransitionState.snapTo(seekableTransitionState.currentState) + } + } + val coroutineScope = rememberCoroutineScope() + var lastNavigatedIndex by remember { + mutableIntStateOf(0) + } + Column { + Slider( + modifier = Modifier.height(48.dp), + value = seekableTransitionState.fraction, + onValueChange = { + coroutineScope.launch { + if (seekableTransitionState.currentState is Screen.Details) { + seekableTransitionState.seekTo(it, Screen.Home) + } else { + // seek to the previously navigated index + seekableTransitionState.seekTo(it, Screen.Details(lastNavigatedIndex)) + } + } + } + ) + SharedTransitionLayout(modifier = Modifier.weight(1f)) { + transition.AnimatedContent { targetState -> + when (targetState) { + Screen.Home -> { + HomeScreen( + this@SharedTransitionLayout, + this@AnimatedContent, + onItemClick = { + coroutineScope.launch { + lastNavigatedIndex = it + seekableTransitionState.animateTo(Screen.Details(it)) + } + } + ) + } + + is Screen.Details -> { + val snack = listSnacks[targetState.id] + DetailsScreen( + targetState.id, + snack, + this@SharedTransitionLayout, + this@AnimatedContent, + onBackPressed = { + coroutineScope.launch { + seekableTransitionState.animateTo(Screen.Home) + } + } + ) + } + } + } + } + } + + // [END android_compose_shared_element_custom_seeking] +} 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 da1d178f..cac68a58 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 @@ -46,7 +46,6 @@ 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 @@ -75,9 +74,9 @@ fun SharedElement_PredictiveBack() { ) { composable("home") { HomeScreen( - navController, this@SharedTransitionLayout, - this@composable + this@composable, + { navController.navigate("details/$it") } ) } composable( @@ -87,11 +86,13 @@ fun SharedElement_PredictiveBack() { val id = backStackEntry.arguments?.getInt("item") val snack = listSnacks[id!!] DetailsScreen( - navController, id, snack, this@SharedTransitionLayout, - this@composable + this@composable, + { + navController.navigate("home") + } ) } } @@ -99,19 +100,19 @@ fun SharedElement_PredictiveBack() { } @Composable -private fun DetailsScreen( - navController: NavHostController, +fun DetailsScreen( id: Int, snack: Snack, sharedTransitionScope: SharedTransitionScope, - animatedContentScope: AnimatedContentScope + animatedContentScope: AnimatedContentScope, + onBackPressed: () -> Unit ) { with(sharedTransitionScope) { Column( Modifier .fillMaxSize() .clickable { - navController.navigate("home") + onBackPressed() } ) { Image( @@ -141,10 +142,10 @@ private fun DetailsScreen( } @Composable -private fun HomeScreen( - navController: NavHostController, +fun HomeScreen( sharedTransitionScope: SharedTransitionScope, - animatedContentScope: AnimatedContentScope + animatedContentScope: AnimatedContentScope, + onItemClick: (Int) -> Unit, ) { LazyColumn( modifier = Modifier @@ -155,7 +156,7 @@ private fun HomeScreen( itemsIndexed(listSnacks) { index, item -> Row( Modifier.clickable { - navController.navigate("details/$index") + onItemClick(index) } ) { Spacer(modifier = Modifier.width(8.dp))