Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add an example of Shared elements that are seekable using SeekableTransitionState #416

Merged
merged 10 commits into from
Dec 10, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

package com.example.compose.snippets.animations.sharedelement

import androidx.activity.compose.PredictiveBackHandler
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.AnimatedVisibilityScope
Expand All @@ -30,7 +31,9 @@ import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.core.ArcMode
import androidx.compose.animation.core.ExperimentalAnimationSpecApi
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.SeekableTransitionState
import androidx.compose.animation.core.keyframes
import androidx.compose.animation.core.rememberTransition
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
Expand Down Expand Up @@ -60,12 +63,15 @@ import androidx.compose.material.icons.outlined.Create
import androidx.compose.material.icons.outlined.Favorite
import androidx.compose.material.icons.outlined.Share
import androidx.compose.material3.Icon
import androidx.compose.material3.Slider
import androidx.compose.material3.Surface
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
Expand All @@ -85,6 +91,8 @@ import androidx.navigation.navArgument
import com.example.compose.snippets.R
import com.example.compose.snippets.ui.theme.LavenderLight
import com.example.compose.snippets.ui.theme.RoseLight
import kotlin.coroutines.cancellation.CancellationException
import kotlinx.coroutines.launch

@Preview
@Composable
Expand Down Expand Up @@ -628,3 +636,91 @@ fun PlaceholderSizeAnimated_Demo() {
}
// [END android_compose_shared_element_placeholder_size]
}

private sealed class Screen {
data object Home : Screen()
data class Details(val id: Int) : Screen()
}

@Preview
@Composable
fun CustomPredictiveBackHandle() {
// [START android_compose_shared_element_custom_seeking]
val seekableTransitionState = remember {
SeekableTransitionState<Screen>(Screen.Home)
}
val transition = rememberTransition(transitionState = seekableTransitionState)

PredictiveBackHandler(seekableTransitionState.currentState is Screen.Details) { progress ->
try {
progress.collect { backEvent ->
// code for progress
try {
seekableTransitionState.seekTo(backEvent.progress, targetState = Screen.Home)
} catch (e: CancellationException) {
// ignore the cancellation
}
}
// code for completion
seekableTransitionState.animateTo(seekableTransitionState.targetState)
} catch (e: CancellationException) {
// code for cancellation
seekableTransitionState.animateTo(seekableTransitionState.currentState)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it slightly confusing why do I need to suppress the cancellation exceptions here.
Can we explain it a bit more in the comments? 🙏🏼

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added comments.

}
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]
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -75,9 +74,9 @@ fun SharedElement_PredictiveBack() {
) {
composable("home") {
HomeScreen(
navController,
this@SharedTransitionLayout,
this@composable
this@composable,
{ navController.navigate("details/$it") }
)
}
composable(
Expand All @@ -87,31 +86,33 @@ 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")
}
)
}
}
}
}

@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(
Expand Down Expand Up @@ -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
Expand All @@ -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))
Expand Down
Loading