Skip to content

Transitions

To use the transitions you should first import cafe.adriel.voyager:voyager-transitions (see Setup).

Voyager has built-in transitions! When initializing the Navigator you can override the default content and use, for example, the SlideTransition.

setContent {
    Navigator(HomeScreen) { navigator ->
        SlideTransition(navigator)
    }
}

Known bug

There is a known bug using any Transition APIs can leaky ScreenModels or ViewModels, this happens because Voyager by default dispose Screens in the next Composition tick after a pop or replace is called, but the transition only finish later, so the ScreenModel or ViewModel is re created or cleared to early. For this purpose since Voyager 1.1.0-beta02 we have introduce a new API to fix this issue. For more details on the issue see #106.

Navigator(
    screen = ...,
    disposeBehavior = NavigatorDisposeBehavior(disposeSteps = false),
) { navigator ->
    SlideTransition(
        navigator = navigator,
        ...
        disposeScreenAfterTransitionEnd = true
    )
}

Warning

Have encounter Screen was used multiple times crash? Provide a uniqueScreenKey for your Screens

class ScreenFoo : Screen {

    override val key: ScreenKey = uniqueScreenKey

    @Composable
    override fun Content() {
        ...
    }

Available transitions

FadeTransition() SlideTransition()
ScaleTransition()

Custom transitions

It’s simple to add your own transitions: call ScreenTransition with a custom transitionModifier. Use the available params (screen, transition and event) to animate as needed.

@Composable
fun MyCustomTransition(
    navigator: Navigator,
    modifier: Modifier = Modifier,
    content: ScreenTransitionContent
) {
    ScreenTransition(
        navigator = navigator,
        modifier = modifier,
        content = content,
        transition = {
            val (initialScale, targetScale) = when (navigator.lastEvent) {
                StackEvent.Pop -> ExitScales
                else -> EnterScales
            }

            scaleIn(initialScale) with scaleOut(targetScale)
        }
    )
}

setContent {
    Navigator(HomeScreen) { navigator ->
        MyCustomTransition(navigator) { screen ->
            screen.Content()
        }
    }
}

Take a look at the source of the available transitions for working examples.

Per Screen transitions [Experimental]

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

class ExampleSlideScreen : Screen, ScreenTransition {
    override val key: ScreenKey
        get() = uniqueScreenKey

    @Composable
    override fun Content() {
        ...
    }

    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)
        }
    }
}

It’s convenient to use Kotlin delegates for per-Screen transitions. For example, you can create a SlideTransition and FadeTransition classes:

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)
        }
    }
}

Then you can use them as delegates in your Screens:

class SlideScreen : Screen, ScreenTransition by SlideTransition() {

    @Composable
    override fun Content() {
        ...
    }
}

class FadeScreen : Screen, ScreenTransition by FadeTransition() {

    @Composable
    override fun Content() {
        ...
    }
}

Also you can use can pass your custom ScreenTransition instance in ScreenTransition function, it will be used for default animation.

setContent {
    Navigator(FadeScreen) { navigator ->
        ScreenTransition(
            navigator = navigator,
            defaultTransition = SlideTransition()
        )
    }
}

The API works with any ScreenTransition API, you just need to provide one and the Per Screen transition should.

setContent {
    Navigator(HomeScreen) { navigator ->
        SlideTransition(navigator)
    }
}

CrossfadeTransition is not supported yet.