Android Navigation Component with Transition from Drawerlayout
Asked Answered
L

2

7

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.

Lechery answered 9/10, 2018 at 14:15 Comment(0)
B
3

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.

Burnoose answered 24/6, 2019 at 14:54 Comment(2)
I tried your implementation. This seems to be exactly what I'm looking for. I have a drawer menu with several fragments that I want to add animation transition to. I added the code to my onCreate method in my MainActivity. The setNavigationItemSelectedListener is not getting called when I make a selection from the Drawer Menu.Warford
@tzg, I suppose that setNavigationItemSelectedListener is not getting called because navigation component either doesn't know about the Drawer or the way navigation was set up is causing the issue. I will extend the answer with the configuration used in my project.Burnoose
N
2

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

Nostoc answered 23/12, 2020 at 11:10 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.