diff --git a/docs/transitions-api.md b/docs/transitions-api.md index d36c253b..9f735a2c 100644 --- a/docs/transitions-api.md +++ b/docs/transitions-api.md @@ -80,9 +80,10 @@ setContent { If you want to define a Enter and Exit transition for a specific Screen, you have a lot of options to do starting from 1.1.0-beta01 Voyager have a new experimental API for this purpose. +To animate the content, we use transitions of the target screen in the case of push navigation, otherwise we use transitions of the initial screen ```kotlin -class ExampleScaleScreen : Screen, ScreenTransition { +class ExampleSlideScreen : Screen, ScreenTransition { override val key: ScreenKey get() = uniqueScreenKey @@ -90,10 +91,20 @@ class ExampleScaleScreen : Screen, ScreenTransition { override fun Content() { ... } - - override fun enter(): EnterTransition? = scaleIn() - override fun exit(): ExitTransition? = scaleOut() + override fun enter(lastEvent: StackEvent): EnterTransition { + return slideIn { size -> + val x = if (lastEvent == StackEvent.Pop) -size.width else size.width + IntOffset(x = x, y = 0) + } + } + + override fun exit(lastEvent: StackEvent): ExitTransition { + return slideOut { size -> + val x = if (lastEvent == StackEvent.Pop) size.width else -size.width + IntOffset(x = x, y = 0) + } + } } ``` diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/SampleActivity.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/SampleActivity.kt index e5fd90aa..7ecbf302 100644 --- a/samples/android/src/main/java/cafe/adriel/voyager/sample/SampleActivity.kt +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/SampleActivity.kt @@ -67,7 +67,7 @@ class SampleActivity : ComponentActivity() { StartSampleButton("LiveData Integration") StartSampleButton("Hilt Integration") StartSampleButton("Legacy Integration") - StartSampleButton("ScreenTransitionActivity") + StartSampleButton("Screen Transition") } } } diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/BaseSampleScreen.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/BaseSampleScreen.kt deleted file mode 100644 index 198bdaf6..00000000 --- a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/BaseSampleScreen.kt +++ /dev/null @@ -1,64 +0,0 @@ -package cafe.adriel.voyager.sample.screenTransition - -import androidx.compose.foundation.layout.Arrangement -import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.Spacer -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.fillMaxWidth -import androidx.compose.foundation.layout.height -import androidx.compose.foundation.layout.padding -import androidx.compose.material.Button -import androidx.compose.material.Text -import androidx.compose.runtime.Composable -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.unit.dp -import cafe.adriel.voyager.core.screen.Screen -import cafe.adriel.voyager.core.screen.ScreenKey -import cafe.adriel.voyager.navigator.LocalNavigator -import cafe.adriel.voyager.navigator.currentOrThrow - -abstract class BaseSampleScreen : Screen { - - abstract val index: Int - - override val key: ScreenKey get() = "SampleScreen$index" - - @Composable - override fun Content() { - val navigator = LocalNavigator.currentOrThrow - - Column( - modifier = Modifier - .fillMaxSize() - .padding(40.dp), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - Text(text = "Screen $index") - - Spacer(modifier = Modifier.height(20.dp)) - - Button( - modifier = Modifier.fillMaxWidth(), - onClick = { - navigator.push( - when { - index % 2 == 0 -> NoCustomAnimationSampleScreen(index = index + 1) - else -> FadeAnimationSampleScreen(index = index + 1) - } - ) - } - ) { - Text(text = "Next") - } - - Button( - modifier = Modifier.fillMaxWidth(), - onClick = { navigator.pop() } - ) { - Text(text = "Back") - } - } - } -} diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/FadeTransition.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/FadeTransition.kt deleted file mode 100644 index 35323612..00000000 --- a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/FadeTransition.kt +++ /dev/null @@ -1,19 +0,0 @@ -package cafe.adriel.voyager.sample.screenTransition - -import androidx.compose.animation.EnterTransition -import androidx.compose.animation.ExitTransition -import androidx.compose.animation.core.tween -import androidx.compose.animation.fadeIn -import androidx.compose.animation.fadeOut -import cafe.adriel.voyager.transitions.ScreenTransition - -class FadeTransition : ScreenTransition { - - override fun enter(isPop: Boolean): EnterTransition { - return fadeIn(tween(500, delayMillis = 500)) - } - - override fun exit(isPop: Boolean): ExitTransition { - return fadeOut(tween(500)) - } -} diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SampleScreen.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SampleScreen.kt deleted file mode 100644 index 17954184..00000000 --- a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SampleScreen.kt +++ /dev/null @@ -1,11 +0,0 @@ -package cafe.adriel.voyager.sample.screenTransition - -import cafe.adriel.voyager.transitions.ScreenTransition - -data class NoCustomAnimationSampleScreen( - override val index: Int -) : BaseSampleScreen() - -data class FadeAnimationSampleScreen( - override val index: Int -) : BaseSampleScreen(), ScreenTransition by FadeTransition() 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 new file mode 100644 index 00000000..a19b0da0 --- /dev/null +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SampleScreens.kt @@ -0,0 +1,68 @@ +package cafe.adriel.voyager.sample.screenTransition + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.core.screen.ScreenKey +import cafe.adriel.voyager.transitions.ScreenTransition + +private val colors = listOf( + Color.Red, + Color.Yellow, + Color.Green, + Color.Blue, + Color.Black +) + +abstract class BaseSampleScreen( + private val transitionType: String +) : Screen { + + abstract val index: Int + + override val key: ScreenKey get() = "SampleScreen$index" + + @Composable + override fun Content() { + Column( + modifier = Modifier + .fillMaxSize() + .background(colors[index % colors.size].copy(alpha = 0.3f)) + .padding(40.dp), + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "Screen $index", + fontSize = 24.sp, + fontWeight = FontWeight.Bold + ) + Spacer(modifier = Modifier.height(8.dp)) + Text( + text = transitionType, + fontSize = 18.sp + ) + } + } +} + +data class NoCustomAnimationSampleScreen( + override val index: Int +) : BaseSampleScreen("Default transition") + +data class FadeAnimationSampleScreen( + override val index: Int +) : BaseSampleScreen("Fade transition"), ScreenTransition by FadeTransition() 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 new file mode 100644 index 00000000..f3ef7ac4 --- /dev/null +++ b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SampleTransitions.kt @@ -0,0 +1,40 @@ +package cafe.adriel.voyager.sample.screenTransition + +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +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.slideOut +import androidx.compose.ui.unit.IntOffset +import cafe.adriel.voyager.core.stack.StackEvent +import cafe.adriel.voyager.transitions.ScreenTransition + +class FadeTransition : ScreenTransition { + + override fun enter(lastEvent: StackEvent): EnterTransition { + return fadeIn(tween(500, delayMillis = 500)) + } + + override fun exit(lastEvent: StackEvent): ExitTransition { + return fadeOut(tween(500)) + } +} + +class SlideTransition : ScreenTransition { + + override fun enter(lastEvent: StackEvent): EnterTransition { + return slideIn { size -> + val x = if (lastEvent == StackEvent.Pop) -size.width else size.width + IntOffset(x = x, y = 0) + } + } + + override fun exit(lastEvent: StackEvent): ExitTransition { + return slideOut { size -> + val x = if (lastEvent == StackEvent.Pop) size.width else -size.width + IntOffset(x = x, y = 0) + } + } +} 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 fe74e58d..18b3439b 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 @@ -3,7 +3,18 @@ package cafe.adriel.voyager.sample.screenTransition import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material.Button +import androidx.compose.material.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.transitions.ScreenTransition @@ -21,11 +32,42 @@ class ScreenTransitionActivity : ComponentActivity() { fun Content() { Navigator( screen = NoCustomAnimationSampleScreen(0) - ) { - ScreenTransition( - navigator = it, - defaultTransition = SlideTransition() - ) + ) { navigator -> + Box(modifier = Modifier.fillMaxSize()) { + ScreenTransition( + navigator = navigator, + defaultTransition = SlideTransition() + ) + + Row( + modifier = Modifier + .align(Alignment.BottomCenter) + .fillMaxWidth() + .padding(40.dp), + horizontalArrangement = Arrangement.spacedBy(20.dp) + ) { + Button( + onClick = { navigator.push(FadeAnimationSampleScreen(navigator.items.size)) }, + modifier = Modifier.weight(1f) + ) { + Text(text = "Fade") + } + + Button( + onClick = { navigator.push(NoCustomAnimationSampleScreen(navigator.items.size)) }, + modifier = Modifier.weight(1f) + ) { + Text(text = "Default") + } + + Button( + onClick = { navigator.pop() }, + modifier = Modifier.weight(1f) + ) { + Text(text = "Pop") + } + } + } } } } diff --git a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SlideTransition.kt b/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SlideTransition.kt deleted file mode 100644 index e541a25e..00000000 --- a/samples/android/src/main/java/cafe/adriel/voyager/sample/screenTransition/SlideTransition.kt +++ /dev/null @@ -1,25 +0,0 @@ -package cafe.adriel.voyager.sample.screenTransition - -import androidx.compose.animation.EnterTransition -import androidx.compose.animation.ExitTransition -import androidx.compose.animation.slideIn -import androidx.compose.animation.slideOut -import androidx.compose.ui.unit.IntOffset -import cafe.adriel.voyager.transitions.ScreenTransition - -class SlideTransition : ScreenTransition { - - override fun enter(isPop: Boolean): EnterTransition { - return slideIn { size -> - val x = if (isPop) -size.width else size.width - IntOffset(x = x, y = 0) - } - } - - override fun exit(isPop: Boolean): ExitTransition { - return slideOut { size -> - val x = if (isPop) size.width else -size.width - IntOffset(x = x, y = 0) - } - } -} diff --git a/voyager-transitions/api/android/voyager-transitions.api b/voyager-transitions/api/android/voyager-transitions.api index a8919bb1..0fd293e7 100644 --- a/voyager-transitions/api/android/voyager-transitions.api +++ b/voyager-transitions/api/android/voyager-transitions.api @@ -50,12 +50,11 @@ 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;Z)Landroidx/compose/animation/EnterTransition; - public static fun exit (Lcafe/adriel/voyager/transitions/ScreenTransition;Z)Landroidx/compose/animation/ExitTransition; + 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 final class cafe/adriel/voyager/transitions/ScreenTransitionKt { - public static final fun ScreenTransition (Lcafe/adriel/voyager/navigator/Navigator;Lcafe/adriel/voyager/transitions/ScreenTransition;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V public static final fun ScreenTransition (Lcafe/adriel/voyager/navigator/Navigator;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V public static final fun ScreenTransition (Lcafe/adriel/voyager/navigator/Navigator;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V } diff --git a/voyager-transitions/api/desktop/voyager-transitions.api b/voyager-transitions/api/desktop/voyager-transitions.api index 9580dc43..1aacfd2e 100644 --- a/voyager-transitions/api/desktop/voyager-transitions.api +++ b/voyager-transitions/api/desktop/voyager-transitions.api @@ -50,12 +50,11 @@ 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;Z)Landroidx/compose/animation/EnterTransition; - public static fun exit (Lcafe/adriel/voyager/transitions/ScreenTransition;Z)Landroidx/compose/animation/ExitTransition; + 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 final class cafe/adriel/voyager/transitions/ScreenTransitionKt { - public static final fun ScreenTransition (Lcafe/adriel/voyager/navigator/Navigator;Lcafe/adriel/voyager/transitions/ScreenTransition;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V public static final fun ScreenTransition (Lcafe/adriel/voyager/navigator/Navigator;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V public static final fun ScreenTransition (Lcafe/adriel/voyager/navigator/Navigator;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Landroidx/compose/ui/Modifier;Lkotlin/jvm/functions/Function4;Landroidx/compose/runtime/Composer;II)V } 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 e7adf198..f934f42a 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 @@ -20,16 +20,18 @@ public interface ScreenTransition { /** * Defines the enter transition for the Screen. * - * Returns null when it should not define a transition for this screen. + * @param lastEvent - lastEvent in the navigation stack. + * @return EnterTransition or null when it should not define a transition for this screen. */ - public fun enter(isPop: Boolean): EnterTransition? = null + public fun enter(lastEvent: StackEvent): EnterTransition? = null /** * Defines the exit transition for the Screen. * - * Returns null when it should not define a transition for this screen. + * @param lastEvent - lastEvent in the navigation stack. + * @return ExitTransition or null when it should not define a transition for this screen. */ - public fun exit(isPop: Boolean): ExitTransition? = null + public fun exit(lastEvent: StackEvent): ExitTransition? = null } public typealias ScreenTransitionContent = @Composable AnimatedVisibilityScope.(Screen) -> Unit @@ -55,6 +57,7 @@ public fun ScreenTransition( ) } +@ExperimentalVoyagerApi @Composable public fun ScreenTransition( navigator: Navigator, @@ -65,9 +68,8 @@ public fun ScreenTransition( ScreenTransition( navigator = navigator, transition = { - val isPop = navigator.lastEvent == StackEvent.Pop - val enter = defaultTransition.enter(isPop) ?: EnterTransition.None - val exit = defaultTransition.exit(isPop) ?: ExitTransition.None + val enter = defaultTransition.enter(navigator.lastEvent) ?: EnterTransition.None + val exit = defaultTransition.exit(navigator.lastEvent) ?: ExitTransition.None enter togetherWith exit }, modifier = modifier, @@ -87,16 +89,13 @@ public fun ScreenTransition( transitionSpec = { val contentTransform = transition() - val isPop = navigator.lastEvent == StackEvent.Pop - - val source = when (navigator.lastEvent) { + val sourceScreenTransition = when (navigator.lastEvent) { StackEvent.Pop, StackEvent.Replace -> initialState else -> targetState - } - - val screenEnterTransition = (source as? ScreenTransition)?.enter(isPop) + } as? ScreenTransition - val screenExitTransition = (source as? ScreenTransition)?.exit(isPop) + val screenEnterTransition = sourceScreenTransition?.enter(navigator.lastEvent) + val screenExitTransition = sourceScreenTransition?.exit(navigator.lastEvent) if (screenExitTransition != null || screenEnterTransition != null) { (screenEnterTransition ?: contentTransform.targetContentEnter) togetherWith