diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SampleScreens.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SampleScreens.kt index ed862748..7ca887dc 100644 --- a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SampleScreens.kt +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SampleScreens.kt @@ -58,10 +58,14 @@ abstract class BaseSampleScreen( val color = remember { colors.getOrNull(index % colors.size) ?: colors.random() } + val contentColor = remember { + color.average() + } + Column( modifier = Modifier .fillMaxSize() - .background(color.copy(alpha = 0.3f)) + .background(color) .padding(40.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally @@ -69,17 +73,23 @@ abstract class BaseSampleScreen( Text( text = "Screen $index", fontSize = 24.sp, - fontWeight = FontWeight.Bold + fontWeight = FontWeight.Bold, + color = contentColor ) Spacer(modifier = Modifier.height(8.dp)) Text( text = transitionType, - fontSize = 18.sp + fontSize = 18.sp, + color = contentColor ) } } } +private fun Color.average(): Color { + return Color((255 - red * 255) / 255, (255 - green * 255) / 255, (255 - blue * 255) / 255, alpha) +} + data class NoCustomAnimationSampleScreen( override val index: Int ) : BaseSampleScreen("Default transition") @@ -87,3 +97,7 @@ data class NoCustomAnimationSampleScreen( data class FadeAnimationSampleScreen( override val index: Int ) : BaseSampleScreen("Fade transition"), ScreenTransition by FadeTransition() + +data class SlideInVerticallyAnimationSampleScreen( + override val index: Int +) : BaseSampleScreen("slide in vertically transition"), ScreenTransition by SlideInVerticallyTransition(index) diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SampleTransitions.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SampleTransitions.kt index f3ef7ac4..61c7d6ef 100644 --- a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SampleTransitions.kt +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SampleTransitions.kt @@ -6,7 +6,9 @@ import androidx.compose.animation.core.tween import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.slideIn +import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOut +import androidx.compose.animation.slideOutVertically import androidx.compose.ui.unit.IntOffset import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.transitions.ScreenTransition @@ -38,3 +40,26 @@ class SlideTransition : ScreenTransition { } } } + +class SlideInVerticallyTransition(val index: Int) : ScreenTransition { + + override fun enter(lastEvent: StackEvent): EnterTransition { + return if (lastEvent == StackEvent.Pop) { + fadeIn(initialAlpha = 0.9f) + } else { + slideInVertically { it } + } + } + + override fun exit(lastEvent: StackEvent): ExitTransition { + return if (lastEvent == StackEvent.Pop) { + slideOutVertically { it } + } else { + fadeOut(targetAlpha = 0.9f) + } + } + + override fun zIndex(lastEvent: StackEvent): Float { + return if (lastEvent == StackEvent.Pop) (index).toFloat() else (index + 1).toFloat() + } +} diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/ScreenTransitionActivity.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/ScreenTransitionActivity.kt index e1da2ff6..9f308b9a 100644 --- a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/ScreenTransitionActivity.kt +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/ScreenTransitionActivity.kt @@ -62,6 +62,19 @@ class ScreenTransitionActivity : ComponentActivity() { Text(text = "Fade") } + Button( + onClick = { navigator.push(SlideInVerticallyAnimationSampleScreen(navigator.items.size)) }, + modifier = Modifier.weight(1f) + ) { + Text(text = "SlideInVertically") + } + } + Row( + modifier = Modifier + .fillMaxWidth() + .padding(10.dp), + horizontalArrangement = Arrangement.spacedBy(20.dp) + ) { Button( onClick = { navigator.push(NoCustomAnimationSampleScreen(navigator.items.size)) }, modifier = Modifier.weight(1f) diff --git a/voyager-transitions/api/android/voyager-transitions.api b/voyager-transitions/api/android/voyager-transitions.api index 9c8fe82d..f458b6f7 100644 --- a/voyager-transitions/api/android/voyager-transitions.api +++ b/voyager-transitions/api/android/voyager-transitions.api @@ -62,6 +62,7 @@ public final class cafe/adriel/voyager/transitions/ScaleTransitionKt { public final class cafe/adriel/voyager/transitions/ScreenTransition$DefaultImpls { public static fun enter (Lcafe/adriel/voyager/transitions/ScreenTransition;Lcafe/adriel/voyager/core/stack/StackEvent;)Landroidx/compose/animation/EnterTransition; public static fun exit (Lcafe/adriel/voyager/transitions/ScreenTransition;Lcafe/adriel/voyager/core/stack/StackEvent;)Landroidx/compose/animation/ExitTransition; + public static fun zIndex (Lcafe/adriel/voyager/transitions/ScreenTransition;Lcafe/adriel/voyager/core/stack/StackEvent;)Ljava/lang/Float; } public final class cafe/adriel/voyager/transitions/ScreenTransitionKt { diff --git a/voyager-transitions/api/desktop/voyager-transitions.api b/voyager-transitions/api/desktop/voyager-transitions.api index f4ecf2b1..9fda2571 100644 --- a/voyager-transitions/api/desktop/voyager-transitions.api +++ b/voyager-transitions/api/desktop/voyager-transitions.api @@ -62,6 +62,7 @@ public final class cafe/adriel/voyager/transitions/ScaleTransitionKt { public final class cafe/adriel/voyager/transitions/ScreenTransition$DefaultImpls { public static fun enter (Lcafe/adriel/voyager/transitions/ScreenTransition;Lcafe/adriel/voyager/core/stack/StackEvent;)Landroidx/compose/animation/EnterTransition; public static fun exit (Lcafe/adriel/voyager/transitions/ScreenTransition;Lcafe/adriel/voyager/core/stack/StackEvent;)Landroidx/compose/animation/ExitTransition; + public static fun zIndex (Lcafe/adriel/voyager/transitions/ScreenTransition;Lcafe/adriel/voyager/core/stack/StackEvent;)Ljava/lang/Float; } public final class cafe/adriel/voyager/transitions/ScreenTransitionKt { diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/FadeTransition.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/FadeTransition.kt index 2e61c0b7..ba7e2ea0 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/FadeTransition.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/FadeTransition.kt @@ -7,8 +7,10 @@ import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.togetherWith import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi +import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.navigator.Navigator @Composable @@ -33,13 +35,17 @@ public fun FadeTransition( navigator: Navigator, modifier: Modifier = Modifier, animationSpec: FiniteAnimationSpec = spring(stiffness = Spring.StiffnessMediumLow), + contentAlignment: Alignment = Alignment.TopStart, disposeScreenAfterTransitionEnd: Boolean = false, + contentKey: (Screen) -> Any = { it.key }, content: ScreenTransitionContent = { it.Content() } ) { ScreenTransition( navigator = navigator, modifier = modifier, + contentAlignment = contentAlignment, disposeScreenAfterTransitionEnd = disposeScreenAfterTransitionEnd, + contentKey = contentKey, content = content, transition = { fadeIn(animationSpec = animationSpec) togetherWith fadeOut(animationSpec = animationSpec) } ) diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScaleTransition.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScaleTransition.kt index 81f8a9cb..2aeaba44 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScaleTransition.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScaleTransition.kt @@ -7,8 +7,10 @@ import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleOut import androidx.compose.animation.togetherWith import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi +import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.Navigator @@ -37,13 +39,17 @@ public fun ScaleTransition( navigator: Navigator, modifier: Modifier = Modifier, animationSpec: FiniteAnimationSpec = spring(stiffness = Spring.StiffnessMediumLow), + contentAlignment: Alignment = Alignment.TopStart, disposeScreenAfterTransitionEnd: Boolean = false, + contentKey: (Screen) -> Any = { it.key }, content: ScreenTransitionContent = { it.Content() } ) { ScreenTransition( navigator = navigator, modifier = modifier, + contentAlignment = contentAlignment, disposeScreenAfterTransitionEnd = disposeScreenAfterTransitionEnd, + contentKey = contentKey, content = content, transition = { val (initialScale, targetScale) = when (navigator.lastEvent) { diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt index c0db8e52..2487f7b9 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/ScreenTransition.kt @@ -7,7 +7,6 @@ import androidx.compose.animation.ContentTransform import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition import androidx.compose.animation.ExperimentalAnimationApi -import androidx.compose.animation.togetherWith import androidx.compose.runtime.Composable import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect @@ -15,6 +14,7 @@ import androidx.compose.runtime.MutableState import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.Saver import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi import cafe.adriel.voyager.core.screen.Screen @@ -39,6 +39,14 @@ public interface ScreenTransition { * @return ExitTransition or null when it should not define a transition for this screen. */ public fun exit(lastEvent: StackEvent): ExitTransition? = null + + /** + * Defines the z-index for the Screen. + * + * @param lastEvent - lastEvent in the navigation stack. + * @return Float value for the z-index. + */ + public fun zIndex(lastEvent: StackEvent): Float? = null } public typealias ScreenTransitionContent = @Composable AnimatedVisibilityScope.(Screen) -> Unit @@ -50,13 +58,17 @@ public fun ScreenTransition( enterTransition: AnimatedContentTransitionScope.() -> ContentTransform, exitTransition: AnimatedContentTransitionScope.() -> ContentTransform, modifier: Modifier = Modifier, + contentAlignment: Alignment = Alignment.TopStart, disposeScreenAfterTransitionEnd: Boolean = false, + contentKey: (Screen) -> Any = { it.key }, content: ScreenTransitionContent = { it.Content() } ) { ScreenTransition( navigator = navigator, modifier = modifier, + contentAlignment = contentAlignment, disposeScreenAfterTransitionEnd = disposeScreenAfterTransitionEnd, + contentKey = contentKey, content = content, transition = { when (navigator.lastEvent) { @@ -77,8 +89,12 @@ public fun ScreenTransition( ) { ScreenTransition( navigator = navigator, - enterTransition = enterTransition, - exitTransition = exitTransition, + transition = { + when (navigator.lastEvent) { + StackEvent.Pop -> exitTransition() + else -> enterTransition() + } + }, modifier = modifier, content = content ) @@ -90,7 +106,10 @@ public fun ScreenTransition( navigator: Navigator, defaultTransition: ScreenTransition, modifier: Modifier = Modifier, + contentZIndex: Float = 0f, + contentAlignment: Alignment = Alignment.TopStart, disposeScreenAfterTransitionEnd: Boolean = false, + contentKey: (Screen) -> Any = { it.key }, content: ScreenTransitionContent = { it.Content() } ) { ScreenTransition( @@ -98,10 +117,16 @@ public fun ScreenTransition( transition = { val enter = defaultTransition.enter(navigator.lastEvent) ?: EnterTransition.None val exit = defaultTransition.exit(navigator.lastEvent) ?: ExitTransition.None - enter togetherWith exit + ContentTransform( + targetContentEnter = enter, + initialContentExit = exit, + targetContentZIndex = contentZIndex + ) }, modifier = modifier, + contentAlignment = contentAlignment, disposeScreenAfterTransitionEnd = disposeScreenAfterTransitionEnd, + contentKey = contentKey, content = content ) } @@ -129,7 +154,9 @@ public fun ScreenTransition( navigator: Navigator, transition: AnimatedContentTransitionScope.() -> ContentTransform, modifier: Modifier = Modifier, + contentAlignment: Alignment = Alignment.TopStart, disposeScreenAfterTransitionEnd: Boolean = false, + contentKey: (Screen) -> Any = { it.key }, content: ScreenTransitionContent = { it.Content() } ) { val screenCandidatesToDispose = rememberSaveable(saver = screenCandidatesToDisposeSaver()) { @@ -163,13 +190,18 @@ public fun ScreenTransition( val screenExitTransition = sourceScreenTransition?.exit(navigator.lastEvent) ?: contentTransform.initialContentExit + val screenContentZIndex = sourceScreenTransition?.zIndex(navigator.lastEvent) + ?: contentTransform.targetContentZIndex + ContentTransform( - screenEnterTransition, - screenExitTransition, - contentTransform.targetContentZIndex, - contentTransform.sizeTransform + targetContentEnter = screenEnterTransition, + initialContentExit = screenExitTransition, + targetContentZIndex = screenContentZIndex, + sizeTransform = contentTransform.sizeTransform ) }, + contentAlignment = contentAlignment, + contentKey = contentKey, modifier = modifier ) { screen -> if (this.transition.targetState == this.transition.currentState && disposeScreenAfterTransitionEnd) { diff --git a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/SlideTransition.kt b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/SlideTransition.kt index 13e1d7c8..a5305bab 100644 --- a/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/SlideTransition.kt +++ b/voyager-transitions/src/commonMain/kotlin/cafe/adriel/voyager/transitions/SlideTransition.kt @@ -10,9 +10,11 @@ import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.IntOffset import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi +import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.stack.StackEvent import cafe.adriel.voyager.navigator.Navigator @@ -47,13 +49,17 @@ public fun SlideTransition( stiffness = Spring.StiffnessMediumLow, visibilityThreshold = IntOffset.VisibilityThreshold ), + contentAlignment: Alignment = Alignment.TopStart, disposeScreenAfterTransitionEnd: Boolean = false, + contentKey: (Screen) -> Any = { it.key }, content: ScreenTransitionContent = { it.Content() } ) { ScreenTransition( navigator = navigator, modifier = modifier, + contentAlignment = contentAlignment, disposeScreenAfterTransitionEnd = disposeScreenAfterTransitionEnd, + contentKey = contentKey, content = content, transition = { val (initialOffset, targetOffset) = when (navigator.lastEvent) {