15

I have 2 actions

Action1

 <action
        android:id="@+id/actionBaseFragmentToAskForLocation"
        app:destination="@+id/introAskForLocationFragment"
        app:enterAnim="@anim/slide_in_right"
        app:exitAnim="@anim/slide_out_left"
        app:popEnterAnim="@anim/slide_in_left"
        app:popExitAnim="@anim/slide_out_right" />

Action2

<action
        android:id="@+id/actionIntroAskLocationToLogin"
        app:destination="@id/loginFragment"
        app:enterAnim="@anim/slide_in_right"
        app:exitAnim="@anim/slide_out_left"
        app:popEnterAnim="@anim/slide_in_right"
        app:popExitAnim="@anim/fade_out"
        app:popUpTo="@+id/app_main_navigation" />

What i want is when the second action is triggered i want to clear the back stack and set only loginFragment to remain in the stack.

just one problem is when i perform the Action2, 'slide_out_right' is performed as exit animation

I understand that if we pop the fragment from the stack the 'popExitAnim' of action1 will be triggered instead of 'exitAnim' of action2.

but i want to know how can I make the fragment perform slide_out_left animation for exiting and also pop it out of the stack.

jaydeep_gedia
  • 484
  • 2
  • 4
  • 13
  • 3
    I've just come across the same problem - did you manage to fix it/work round it? – safarmstrong Jan 15 '19 at 14:58
  • 6
    Seems it's a bug in the Navigation Component - I raised [this issue](https://issuetracker.google.com/issues/122892906) which turned out to be a duplicate of [this issue](https://issuetracker.google.com/issues/110433603). – safarmstrong Jan 17 '19 at 14:43
  • 1
    okay man thanks for the information. – jaydeep_gedia Jan 22 '19 at 06:04
  • 1
    If you don't use animations, popUpTo works great. But with animations, I'm also seeing what you describe. If you specify a different popExitAnim for the action that gets popped as a result of popUpTo, you can sort of work around the problem. But then you end up breaking normal back button popping. So you can't win. I hope this is fixed soon. – Greg Moens Mar 18 '19 at 20:25
  • Are the animations xml sets? I updated to 2.1.0 and now my enterAnims are flickering (alpha fade in + translation set). It appears to not like sets any more, alpha fade in on it's own is fine, translation on it's own is fine. – Daniel Wilson Mar 21 '19 at 11:02
  • Any solution? Another problem now has occurred when navigating to an activity – Hayk Mkrtchyan Aug 05 '21 at 11:15

2 Answers2

11

I ended up overriding onCreateAnimation in the fragment that calls navigate. This exemple shows how to navigate through nested nav graphs by ID and replace the pop exit animation (or popExitAnim) conditionnally.

override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
    val navController = findNavController()
    val graph = navController.graph.findNode(R.id.onboardingGraph) as NavGraph
    val dest = graph.findNode(R.id.confirmationFragment)
    if (!enter && dest != null && navController.currentDestination?.id == dest.id) {
        return AnimationUtils.loadAnimation(requireContext(), R.anim.slide_out_left)
    }
    return super.onCreateAnimation(transit, enter, nextAnim)
}

Note that this particular situation is partly due to the directional nature of slide animations.

maxbeaudoin
  • 6,546
  • 5
  • 38
  • 53
  • I have very limited knowledge about the state of the navigation library, but I think they must have fixed the issue internally, and the code in this answer can now **in some cases itself be problematic**. Creating and returning a custiom slide animation caused a screen flicker for me, that went away when I removed `public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) {...}` – avalancha Nov 27 '20 at 09:02
3

This is bit of a tough one to solve due to NavOptions being internally handled by the convenience methods used in binding your drawer to the navigation graph. I originally tested this solution with the settings menu and onOptionsItemSelected but the basic idea should work here as well.

First, make sure your menu item IDs correspond to those of your navigation fragments:

<menu xmlns:android="http://schemas.android.com/apk/res/android">

    ...

    <item android:id="@+id/example_id" ... />
</menu>
<navigation xmlns:android="http://schemas.android.com/apk/res/android" ... >

    ...

    <fragment android:id="@+id/example_id" ... />
</navigation>

Now, rather than using the ready-made methods for connecting the drawer to your NavController Implement NavigationView.OnNavigationItemSelectedListener in your NavHost activity and override the method onNavigationItemSelected like so:

@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
    NavHost navHost = Navigation.findNavController(this, R.id.your_nav_host_fragment);
    return NavigationUI.onNavDestinationSelected(item, navHost);
}

This will forward the selection as a navigation in your graph. Replace your_nav_host_fragment with the fragment ID on which you set app:defaultNavHost="true".

You will notice that while this works, it still defaults to the slide animations. This is because the NavigationUI call internally creates its own NavOptions with these settings:

NavOptions.Builder builder = new NavOptions.Builder()
                .setLaunchSingleTop(true)
                .setEnterAnim(R.anim.nav_default_enter_anim)
                .setExitAnim(R.anim.nav_default_exit_anim)
                .setPopEnterAnim(R.anim.nav_default_pop_enter_anim)
                .setPopExitAnim(R.anim.nav_default_pop_exit_anim);

Unfortunately the method does not yet take a NavOptions.Builder as an argument, but you can create an utility class based on the Android source code to mimic the functionality:

public class NavigationUIHelper {
    public static boolean onNavDestinationSelected(@NonNull MenuItem item,
                                                   @NonNull NavController navController,
                                                   @NonNull NavOptions.Builder builder) {
        if ((item.getOrder() & Menu.CATEGORY_SECONDARY) == 0) {
            NavDestination destination = findStartDestination(navController.getGraph());
            builder.setPopUpTo(destination.getId(), false);
        }
        NavOptions options = builder.build();
        try {
            navController.navigate(item.getItemId(), null, options);
            return true;
        } catch (IllegalArgumentException e) {
            return false;
        }
    }

    // Need to copy this private method as well
    private static NavDestination findStartDestination(@NonNull NavGraph graph) {
        NavDestination startDestination = graph;
        while (startDestination instanceof NavGraph) {
            NavGraph parent = (NavGraph) startDestination;
            startDestination = parent.findNode(parent.getStartDestination());
        }
        return startDestination;
    }
}

Finally, in your activity you can now replace the call to NavigationUI with the one implemented in NavigationUIHelper:

@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
    NavHost navHost = Navigation.findNavController(this, R.id.your_nav_host_fragment);
    NavOptions.Builder builder = new NavOptions.Builder()
                .setLaunchSingleTop(true)
                .setEnterAnim(R.anim.custom_enter)
                .setExitAnim(R.anim.custom_exit)
                .setPopEnterAnim(R.anim.custom_pop_enter)
                .setPopExitAnim(R.anim.custom_pop_exit);
    return NavigationUIHelper.onNavDestinationSelected(item, navHost, builder);
}

This should allow you to change the drawer transition animations according to your liking without having to replace the Navigation component.

Jonatan
  • 31
  • 4