Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Navigation Component pop to transition issue

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.

like image 702
jaydeep_gedia Avatar asked Dec 30 '18 10:12

jaydeep_gedia


2 Answers

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.

like image 127
maxbeaudoin Avatar answered Nov 14 '22 04:11

maxbeaudoin


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.

like image 3
Jonatan Avatar answered Nov 14 '22 04:11

Jonatan