How can I change the animation between tab switches with a bottomNavigationView?

Viewed 189

I have just updated my code to use the latest 2.4.0-alpha05 for the navigation component and I have custom navigation between the multiple stacks and my main nav graph is like the documentation I found.

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    app:startDestination="@+id/accounts"
    android:id="@+id/bottom_nav">
    <inclue app:graph="@navigation/accounts_tab_nav"/>
    <include app:graph="@navigation/contact_tab_nav" />
    <include app:graph="@navigation/profile_tab_nav" />
</navigation>

Most of my stacks animate with a slide from right to left. It looks like that when I am on the second screen, in let's say the profile screen, and then switch to the first tab it triggers the popEnter en popExitAnim that are defined in the action that leads to the second screen in the profile tab. Like so:

<fragment
    android:id="@+id/profileMain"
    android:name="com.app.ProfileFragment"
    tools:layout="@layout/fragment_profile">
    <action
        android:id="@+id/action_profileMain_to_secondFragment"
        app:destination="@id/secondFragment"
        app:enterAnim="@anim/slide_in_right"
        app:exitAnim="@anim/slide_out_left"
        app:popEnterAnim="@anim/slide_in_left"
        app:popExitAnim="@anim/slide_out_right" />
</fragment>

But obviously I want tho use the (default) fade animation when switching tabs. So how should I do that?

And I would like to pop to the root of the stack when reselecting a tab. But I probably have to do that myself?

2 Answers

I came up with a solution that seems to work for me, but I have to admit it feels a little bit hacky.

I have a public flag in my MainActivity:

var tabWasSelected = false

Then I remember if a tab item was selected in setOnItemSelectedListener

// always show selected Bottom Navigation item as selected (return true)
bottomNavigationView.setOnItemSelectedListener { item ->      
  // In order to get the expected behavior, you have to call default Navigation method manually
  NavigationUI.onNavDestinationSelected(item, navController)

  // set flag so that the fragment can call the correct animation on tab change in onCreateAnimation
  tabWasSelected = true

  return@setOnItemSelectedListener true
}

This will always select the item and navigate to the associated destination while maintaining multiple back stacks.

I then have created a Fragment class which all my other fragments inherit from. It simply overrides onCreateAnimation to set the correct animation. Here is what it looks like:

open class MyFragment: Fragment() {
    
    override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
        val activity = requireActivity()
        if (activity is MainActivity) {
            if (activity.tabWasSelected) {
                if (enter) {
                    //flow is exit first, then enter, so we have to reset the flag on enter               
                    //in order that following animations will run as defined in the nav graph
                    activity.tabWasSelected = false
                    return AnimationUtils.loadAnimation(requireContext(), R.anim.nav_default_pop_enter_anim)
                } else {
                    return AnimationUtils.loadAnimation(requireContext(), R.anim.nav_default_pop_exit_anim)
                }
            }
        }
        //no tab was selected, so run the defined animation
        return super.onCreateAnimation(transit, enter, nextAnim)
    }
}

Instead of using setupWithNavController function, follow this way.

First, create your NavOptions which include animation shown below.

val options = NavOptions.Builder()
       .setLaunchSingleTop(true)
       .setEnterAnim(R.anim.enter_from_bottom)
       .setExitAnim(R.anim.exit_to_top)
       .setPopEnterAnim(R.anim.enter_from_top)
       .setPopExitAnim(R.anim.exit_to_bottom)
       .setPopUpTo(navController.graph.startDestination,false)
       .build();

Then use setOnNavigationItemSelectedListener to navigate with animation like that.

bottomNavigationView.setOnNavigationItemSelectedListener 
    { item ->
    when(item.itemId) {
        R.id.fragmentFirst -> {
       navController.navigate(R.id.fragmentFirst,null,options)
        }
        R.id.fragmentSecond -> {
        navigate(R.id.fragmentSecond,null,options)
        }
        R.id.fragmentThird ->  {
       navController.navigate(R.id.fragmentThird,null,options)
        }
    }
}

Finally, you should prevent same item selection case so you can add below code.

bottomNavigationView.setOnNavigationItemReselectedListener
 { item ->
    return@setOnNavigationItemReselectedListener
 }

I used bottomNavigation like that in my project to add animation for page transitions. I hope it helped.

Related