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

Animated Style pass custom variable #41

Open
pesjak opened this issue Jan 23, 2022 · 9 comments
Open

Animated Style pass custom variable #41

pesjak opened this issue Jan 23, 2022 · 9 comments
Labels
enhancement New feature or request

Comments

@pesjak
Copy link

pesjak commented Jan 23, 2022

Amazing work with the library, it really makes things easier!

One question, if you want animations between screen you can create custom style for each screen that overrides the default AnimatedNavHostEngine you can define for all screens in that graph. But in styles I can't seem to find a way to pass a variable to it. For example in the documentation under ProfileTransitions.kt you have hardcoded initialOffsetX = 1000, but what if I wanted a different number (for example the exact width of the screen. I can define this for default animations, but not for each individual screen.

For example:

     ProvideWindowInsets {
        AppTheme {

            val navController = rememberAnimatedNavController()
            val viewModel: CustomViewModel = hiltViewModel()

            // We can get width of the screen this way
            BoxWithConstraints {
                val width = constraints.maxWidth
                val navHostEngine = rememberAnimatedNavHostEngine(
                    rootDefaultAnimations = RootNavGraphDefaultAnimations(
                        enterTransition = {
                            slideInHorizontally(
                                initialOffsetX = { width },
                                animationSpec = tween(
                                    durationMillis = 700,
                                    easing = FastOutSlowInEasing
                                )
                            )
                        },
                        exitTransition = {
                            slideOutHorizontally(
                                targetOffsetX = { -width },
                                animationSpec = tween(
                                    durationMillis = 700,
                                    easing = FastOutSlowInEasing
                                )
                            )
                        },
                        popEnterTransition = {
                            slideInHorizontally(
                                initialOffsetX = { -width },
                                animationSpec = tween(
                                    durationMillis = 700,
                                    easing = FastOutSlowInEasing
                                )
                            )
                        },
                        popExitTransition = {
                            slideOutHorizontally(
                                targetOffsetX = { width },
                                animationSpec = tween(
                                    durationMillis = 700,
                                    easing = FastOutSlowInEasing
                                )
                            )
                        }
                    ),
                )

                DestinationsNavHost(
                    navGraph = NavGraphs.root,
                    startRoute = NavGraphs.root.startRoute,
                    engine = navHostEngine,
                    navController = navController,
                    modifier = Modifier
                ) {

                    animatedComposable(AScreenDestination) {
                        AScreen(
                            modifier = Modifier.statusBarsPadding(),
                            viewModel = viewModel,
                          )
                    }

                    animatedComposable(BScreenDestination) {
                        BScreen(
                            modifier = Modifier.statusBarsPadding(),
                            viewModel = viewModel,
                          )
                    }
                }
            }
    }
}

In this case both will have default, then If I wanted to change the transition of BScreen, you would need to add:


object BScreenTransition: DestinationStyle.Animated {

    override fun AnimatedContentScope<NavBackStackEntry>.enterTransition(): EnterTransition? {
        return when (initialState.navDestination) {
            BScreenTransitionDestination ->
                slideInHorizontally(
                    initialOffsetX = { 1000 },
                    animationSpec = tween(2500)
                )
            else -> null
        }
    }

    override fun AnimatedContentScope<NavBackStackEntry>.exitTransition(): ExitTransition? {
        return when (targetState.navDestination) {
            BScreenTransitionDestination ->
                slideOutHorizontally(
                    targetOffsetX = { -1000 },
                    animationSpec = tween(2500)
                )
            else -> null
        }
    }

    override fun AnimatedContentScope<NavBackStackEntry>.popEnterTransition(): EnterTransition? {
        return when (initialState.navDestination) {
            BScreenTransitionDestination  ->
                slideInHorizontally(
                    initialOffsetX = { -1000 },
                    animationSpec = tween(2500)
                )
            else -> null
        }
    }

    override fun AnimatedContentScope<NavBackStackEntry>.popExitTransition(): ExitTransition? {
        return when (targetState.navDestination) {
            BScreenTransitionDestination ->
                slideOutHorizontally(
                    targetOffsetX = { 1000 },
                    animationSpec = tween(2500)
                )
            else -> null
        }
    }
}

Then in BScreen add:

@Destination(
    style = BScreenTransition::class,
)

So the animation style would be applied.
This way you can't pass the width to the BScreenTransition you can only change a fixed number inside this object.
Maybe it can be done and I just don't know how.

Thanks for the help!

@raamcosta
Copy link
Owner

Hi @pesjak !

At the moment I don't think there is a way to do this other than saving the screen size somewhere accessible from the transition classes.

Is the screen size all you need? Or so you specifically need arbitrary arguments?

Thanks for reporting 👍

@raamcosta raamcosta added the enhancement New feature or request label Jan 23, 2022
@pesjak
Copy link
Author

pesjak commented Jan 24, 2022

I think I don't need anything else. These animations that you can create are sometimes weird for me, because if you want to have a clear transition from A->B, you need to calculate the width and pass it as offset, because they both need to exist. That is why I wanted to pass the width, but I'll survive :).

No problem, thanks for the reply and again, amazing work @raamcosta!

@raamcosta
Copy link
Owner

I will check a way to get screen size in all transitions then 🙂
Tbh I focused only on samples to get the current animations APIs, so I'm glad you're trying them so I can iterate on your feedback!

I'll leave this open until I have time to check it.

@nikolaDrljaca
Copy link

First of all, amazing work and amazing library!

I've tinkered a bit with it for the past few days, and I've also encountered this issue. A quick and dirty fix might be to use the android system resources to get the screen width/height:

    val height = Resources.getSystem().displayMetrics.heightPixels
    val width = Resources.getSystem().displayMetrics.widthPixels

The only issue here is that the DisplayMetrics don't account for the sizes of system bars, like the navigation bar and the top system bar. For horizontal transitions this is fine(I don't think there are any system bars impacting the width of the screen), but for height based transitions this is something to be aware of.

@raamcosta
Copy link
Owner

Thank you @nikolaDrljaca !

I'll try to look at this soon.

@Leeonardoo
Copy link

I also have found just now that there's no easy way to do that for now. It would be very nice if there's a way to get LocalDensity.current to create those animations. I'm trying to use this library https://github.com/fornewid/material-motion-compose/ so for now I'm doing a ugly hack using DestinationStyle.Runtime

@TomaszSieber
Copy link

TomaszSieber commented Dec 23, 2022

I would also appreciate a way to pass arguments to be able to do an animation from A to B, where the Screen-Position of A is needed. I am trying to create a similiar animation like in the Google Photos App. I have a Grid with Items, and if the User clicks on one of the Items, then there should be an animation from the Grid-Item to the Detail-View of it. Herefore it would be nice to pass the offset and the scale from the Griditem, and do the transition to the Detail Page based on that.

So I could do something similiar to this:

enterTransition = {
                slideIn(initialOffset = { fullSize ->
                    targetState.center.offsetFrom(fullSize)
                }) +
                        scaleIn(initialScale = targetState.scale) + fadeIn()
            },
            exitTransition = {
                slideOut(targetOffset = { fullSize ->
                    initialState.center.offsetFrom(fullSize)
                }) +
                        scaleOut(targetScale = initialState.scale) + fadeOut()
            }

where initialState is the Grid-Item.

@raamcosta
Copy link
Owner

raamcosta commented Feb 26, 2024

Hi guys!

So, the solution I came up with here was to allow us to add animations at runtime to allow you guys to have the same capabilities you do with official compose navigation. In this case, we lost something when using annotations because the animation functions run outside the context of the NavHost call.
So, in these cases, we will on v2 be able to do this:

    DestinationsNavHost(
        //...
    ) {
        MyScreenDestination.animateWith(
            enterTransition = { /*...*/ },
            exitTransition = { /*...*/ },
            popEnterTransition = { /*...*/ },
            popExitTransition = { /*...*/ }
        )

        // OR you can also pass any instance of DestinationStyle.Animated declared somewhere else
        
        MyScreenDestination animateWith SomeDestinationStyleAnimated

        //...
    }

What do you guys think? This allows you to have a Compose based state above DestinationsNavHost call and do conditional logic inside the animations.

@Leeonardoo
Copy link

@raamcosta looks like it’s exactly what I wanted. That’s perfect to me

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
Development

No branches or pull requests

5 participants