Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android Navigation Component with Transition from Drawerlayout

Is it possible to change the transition effect from opening a fragment from a drawerlayout with the android navigation component. There is nothing in the android docs.

like image 804
Sarah Pöhler Avatar asked Oct 09 '18 14:10

Sarah Pöhler


People also ask

How do I go back to previous fragment in navigation component?

Use Up or Back button to go to a previous step of the order flow.


2 Answers

Sarah! Yes, it is possible. You can add your custom listener to handle navigation item selection and add animation there. I had to add one myself, for other purposes, but it definitely fits as a solution for your task.

How to:

  1. Add layout with drawer. Example:
<?xml version="1.0" encoding="utf-8"?>
<androidx.drawerlayout.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
   xmlns:app="http://schemas.android.com/apk/res-auto"
   xmlns:tools="http://schemas.android.com/tools"
   android:id="@+id/drawer_layout"
   android:layout_width="match_parent"
   android:layout_height="match_parent"
   android:fitsSystemWindows="true"
   tools:openDrawer="start">

   <!-- Other views can be added here, or below -->

   <com.google.android.material.navigation.NavigationView
       android:id="@+id/nav_view"
       android:layout_width="wrap_content"
       android:layout_height="match_parent"
       android:layout_gravity="start"
       app:menu="@menu/menu_for_drawer" />

</androidx.drawerlayout.widget.DrawerLayout>
  1. Find NavigationView by ID and assign it to a variable private NavigationView navigationView in your activity, that you may extract by using findViewById(R.id.nav_view). This step is optional. You may find the view and assing naviation item selection listener without holding a reference to NavigationView;
  2. Set navigation item selection listener:
navigationView.setNavigationItemSelectedListener(menuItem -> {
           @IdRes
           int id = menuItem.getItemId();
           NavOptions.Builder optionsBuilder = new NavOptions.Builder();

           switch (id) {
               case R.id.first_menu_item_id: {
                   // Lets assume for the first menu item navigation is default
                   optionsBuilder
                           .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);
               }
               break;
               case R.id.second_menu_item_id: {
                   // Lets assume for the second menu item navigation is missing
                   // empty here
               }
               break;
               case R.id.thrid_menu_item_id: {
                   // Lets assume for the third menu item navigation is custom
                   optionsBuilder
                           .setEnterAnim(R.anim.slide_in_right)
                           .setExitAnim(R.anim.slide_out_left)
                           .setPopEnterAnim(R.anim.slide_in_left)
                           .setPopExitAnim(R.anim.slide_out_right);
               }
               break;
           }

           navController.navigate(id, null, optionsBuilder.build());
           
           // Do not forget to close the drawer
           // drawer.closeDrawers();
           return true;
       });

This should help! Any thoughts and questions are welcome!


In case you are interested in animations mentioned in example:

  • slide_in_left.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <translate android:fromXDelta="-100%" android:toXDelta="0%"
        android:fromYDelta="0%" android:toYDelta="0%"
        android:duration="300"/>
</set>
  • slide_in_right.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <translate android:fromXDelta="100%" android:toXDelta="0%"
        android:fromYDelta="0%" android:toYDelta="0%"
        android:duration="300"/>
</set>
  • slide_out_left.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <translate android:fromXDelta="0%" android:toXDelta="-100%"
        android:fromYDelta="0%" android:toYDelta="0%"
        android:duration="300"/>
</set>
  • slide_out_right.xml
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android">

    <translate android:fromXDelta="0%" android:toXDelta="100%"
        android:fromYDelta="0%" android:toYDelta="0%"
        android:duration="300"/>
</set>

Update (28 March 2020)

In case this solution doesn't work with the Drawer Menu try to use it with the following code.

Note that I placed setNavigationItemSelectedListener call at the end of the method due to NavigationUI functions sometimes settings it's own listener. That could also be the cause of problem.

There is a lot going on behind NavigationUI functions and if something doesn't work right check out implementation of the functions you use.

private void setupNavigation() {
        // Set custom toolbar as action bar
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
                this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
        drawer.addDrawerListener(toggle);
        toggle.syncState();

        // Setup navigation with toolbar and drawer
        NavController navController = Navigation.findNavController(this, R.id.main_nav_host_fragment);

        /* The set of destinations by id considered at the top level 
        of your information hierarchy. The Up button will not be displayed 
        when on these destinations. */
        Set<Integer> set = new HashSet<>(Arrays.asList(R.id.first_fragmentId_from_nav_graph, R.id.second_fragmentId_from_nav_graph, R.id.third_fragmentId_from_nav_graph)); 
        /* Configuration options for {@link NavigationUI} methods that interact with implementations of the
        app bar pattern */
        AppBarConfiguration configuration = new AppBarConfiguration.Builder(set).setDrawerLayout(drawer).build();

        NavigationUI.setupWithNavController(toolbar, navController, configuration);
        NavigationUI.setupWithNavController(navigationView, navController);
        NavigationUI.setupActionBarWithNavController(this, navController, configuration);
        
        // And here you set the listener.
        navigationView.setNavigationItemSelectedListener(...);
    }

There are multiple calls to NavigationUI functions like setupWithNavController and setupActionBarWithNavController. The reason is that behind each one of them is added one more destination changed listener to navigation controller. See addOnDestinationChangedListener for more information about the listener.

  1. First call adds new ToolbarOnDestinationChangedListener(toolbar, configuration);
  2. Second call adds custom implementation of NavController.OnDestinationChangedListener that updates your navigation view in drawer correctly;
  3. Third call adds new ActionBarOnDestinationChangedListener(activity, configuration).

This configuration allows me to use a single application on a tablet with a Master-Detail pattern and on the phone as a usual application with Drawer Menu.

Referenced string resources are just:

<string name="navigation_drawer_open">Open navigation drawer</string>
<string name="navigation_drawer_close">Close navigation drawer</string>

But they are required for accessibility. It's very good to translate them if you support multiple languages. Basically anything used for accessibility is generally good to translate.

like image 150
Jenea Vranceanu Avatar answered Oct 11 '22 16:10

Jenea Vranceanu


There is a simpler way. Internally NavigationUI builds the NavOptions like this:

    if (navController.getCurrentDestination().getParent().findNode(item.getItemId())
            instanceof ActivityNavigator.Destination) {
        builder.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);

    } else {
        builder.setEnterAnim(R.animator.nav_default_enter_anim)
                .setExitAnim(R.animator.nav_default_exit_anim)
                .setPopEnterAnim(R.animator.nav_default_pop_enter_anim)
                .setPopExitAnim(R.animator.nav_default_pop_exit_anim);
    }

You can just create these directories yourself and override the defaults. In my case I had to create the animator folder (not anim!) in /res/ and put 4 new animation files with these names in it. But maybe try both folders to be sure.
At the moment it works perfectly in a MaterialComponents Theme: the selected drawer item still get's highlighted and the appbar animation is still working too (unlike with some other methods).

like image 44
einUsername Avatar answered Oct 11 '22 18:10

einUsername