Search code examples
androidkotlinnavigation-drawerandroid-toolbarandroid-navigation

Animate fragment transitions with using Navigation drawer or Toolbar menu


I have a Navigation drawer and Toolbar menu where I can go to another fragment. I used this to navigate:

override fun onNavigationItemSelected(item: MenuItem): Boolean
{
    drawerLayout.close()
    return NavigationUI.onNavDestinationSelected(
        item,
        findNavController(R.id.navHostFragment)
    )
}

How I can add custom animation while switching fragments?


It is Answer your own question. I have faced this problem and couldn't find any answer which would work 100% well in my case so maybe this will help somebody.


Solution

  • This is how the overridden method onNavigationItemSelected should looks:

    override fun onNavigationItemSelected(item: MenuItem): Boolean
    {
        drawerLayout.close()
    
        // this part checks if current fragment is the same as destination
        return if (findNavController(R.id.navHostFragment).currentDestination?.id != item.itemId)
        {
            val builder = NavOptions.Builder()
                .setLaunchSingleTop(true)
                .setEnterAnim(R.anim.enter_left_to_right)
                .setExitAnim(R.anim.exit_right_to_left)
                .setPopEnterAnim(R.anim.popenter_right_to_left)
                .setPopExitAnim(R.anim.popexit_left_to_right)
    
            // this part set proper pop up destination to prevent "looping" fragments
            if (item.order and Menu.CATEGORY_SECONDARY == 0)
            {
                var startDestination: NavDestination? =
                    findNavController(R.id.navHostFragment).graph
    
                while (startDestination is NavGraph)
                {
                    val parent = startDestination
                    startDestination = parent.findNode(parent.startDestination)
                }
    
                builder.setPopUpTo(
                    startDestination!!.id,
                    false
                )
            }
    
            val options = builder.build()
            return try
            {
                findNavController(R.id.navHostFragment).navigate(item.itemId, null, options)
                true
            }
            catch (e: IllegalArgumentException) // couldn't find destination, do nothing
            {
                false
            }
        }
        else
        {
            false
        }
    }
    
    1. This method prevents going to current, selected fragments. So when a user is in the Fragment A and in the Navigation drawer again selects Fragment A nothing will happen, Navigation drawer will just hide.

    2. This method also prevents "looping" fragments, there could be only one fragment on the stack which was selected from the Navigation drawer or Toolbar menu. E.g. in the navigation drawer is Fragment A and Fragment B. The user is in Fragment Home then go to Fragment A and next to Fragment B. If a user clicks back button app will go back to Fragment Home, not to the Fragment A. Also, instead of finding startDestination in a while loop, it is possible to use R.id.homeFragment. If anyone wants to allow "looping" fragments just delete this part in code.

    I have tested this solution and it seems to work well but of course there can be something which doesn't work exactly as it should.


    If someone wants simple animation with fade in/fade out and vertical translation:

    R.anim.enter_left_to_right

    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <translate
            android:duration="@integer/fragment_anim_time"
            android:fromXDelta="-100%"
            android:fromYDelta="0%"
            android:toXDelta="0%"
            android:toYDelta="0%" />
        <alpha
            android:duration="@integer/fragment_anim_time"
            android:fromAlpha="0.5"
            android:toAlpha="1" />
    </set>
    

    R.anim.exit_right_to_left

    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <translate
            android:duration="@integer/fragment_anim_time"
            android:fromXDelta="0%"
            android:fromYDelta="0%"
            android:toXDelta="100%"
            android:toYDelta="0%" />
        <alpha
            android:duration="@integer/fragment_anim_time"
            android:fromAlpha="1"
            android:toAlpha="0.5" />
    </set>
    

    R.anim.popenter_right_to_left

    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <translate
            android:duration="@integer/fragment_anim_time"
            android:fromXDelta="100%"
            android:fromYDelta="0%"
            android:toXDelta="0%"
            android:toYDelta="0%" />
        <alpha
            android:duration="@integer/fragment_anim_time"
            android:fromAlpha="0.5"
            android:toAlpha="1" />
    </set>
    

    R.anim.popexit_left_to_right

    <set xmlns:android="http://schemas.android.com/apk/res/android">
        <translate
            android:duration="@integer/fragment_anim_time"
            android:fromXDelta="0%"
            android:fromYDelta="0%"
            android:toXDelta="-100%"
            android:toYDelta="0%" />
        <alpha
            android:duration="@integer/fragment_anim_time"
            android:fromAlpha="1"
            android:toAlpha="0.5" />
    </set>
    

    res/values/integers.xml

    <integer name="fragment_anim_time">250</integer>