How do I define default animations for Navigation Actions?

前端 未结 3 742
情话喂你
情话喂你 2020-12-24 01:32

I\'m using Android Studio 3.2 Canary 14 and The Navigation Architecture Component. With this you can define transition animations pretty much as you would when using Intents

相关标签:
3条回答
  • 2020-12-24 01:57

    R.anim has the default animations defined (as final):

    • nav_default_enter_anim

    • nav_default_exit_anim

    • nav_default_pop_enter_anim

    • nav_default_pop_exit_anim

    in order to change this behavior, you would have to use custom NavOptions,

    because this is where those animation are being assigned to a NavAction.

    one can assign these with the NavOptions.Builder:

    protected NavOptions getNavOptions() {
    
        NavOptions navOptions = new NavOptions.Builder()
          .setEnterAnim(R.anim.default_enter_anim)
          .setExitAnim(R.anim.default_exit_anim)
          .setPopEnterAnim(R.anim.default_pop_enter_anim)
          .setPopExitAnim(R.anim.default_pop_exit_anim)
          .build();
    
        return navOptions;
    }
    

    most likely one would need to create a DefaultNavFragment, which extends class androidx.navigation.fragment (the documentation there does not seem completed yet).

    So you can pass these NavOptions to the NavHostFragment like this:

    NavHostFragment.findNavController(this).navigate(R.id.your_action_id, null, getNavOptions());
    

    alternatively... when looking at the attrs.xml of that package; these animations are style-able:

    <resources>
        <declare-styleable name="NavAction">
            <attr name="enterAnim" format="reference"/>
            <attr name="exitAnim" format="reference"/>
            <attr name="popEnterAnim" format="reference"/>
            <attr name="popExitAnim" format="reference"/>
            ...
        </declare-styleable>
    </resources>
    

    this means, one can define the according styles - and define these, as part of the theme...

    one can define them in styles.xml:

    <style name="Theme.Default" parent="Theme.AppCompat.Light.NoActionBar">
    
        <!-- these should be the correct ones -->
        <item name="NavAction_enterAnim">@anim/default_enter_anim</item>
        <item name="NavAction_exitAnim">@anim/default_exit_anim</item>
        <item name="NavAction_popEnterAnim">@anim/default_pop_enter_anim</item>
        <item name="NavAction_popExitAnim">@anim/default_pop_exit_anim</item>
    
    </style>
    
    0 讨论(0)
  • 2020-12-24 02:15

    As said, R.anim has the default animations defined:

    • nav_default_enter_anim

    • nav_default_exit_anim

    • nav_default_pop_enter_anim

    • nav_default_pop_exit_anim

    But you can easily override them.

    Just create your own four anim resources with the same names in your app module (just to clarify, the id of one of them is your.package.name.R.anim.nav_default_enter_anim) and write what animation you'd like.

    0 讨论(0)
  • 2020-12-24 02:16

    It is possible with custom androidx.navigation.fragment.Navigator.

    I will demonstrate how to override fragment navigation. Here is our custom navigator. Pay attention to setAnimations() method

    @Navigator.Name("fragment")
    class MyAwesomeFragmentNavigator(
        private val context: Context,
        private val manager: FragmentManager, // Should pass childFragmentManager.
        private val containerId: Int
    ): FragmentNavigator(context, manager, containerId) {
    private val backStack by lazy {
        this::class.java.superclass!!.getDeclaredField("mBackStack").let {
            it.isAccessible = true
            it.get(this) as ArrayDeque<Integer>
        }
    }
    
    override fun navigate(destination: Destination, args: Bundle?, navOptions: NavOptions?, navigatorExtras: Navigator.Extras?): NavDestination? {
        if (manager.isStateSaved) {
            logi("Ignoring navigate() call: FragmentManager has already"
                    + " saved its state")
            return null
        }
        var className = destination.className
        if (className[0] == '.') {
            className = context.packageName + className
        }
        val frag = instantiateFragment(context, manager,
                className, args)
        frag.arguments = args
        val ft = manager.beginTransaction()
    
        navOptions?.let { setAnimations(it, ft) }
    
        ft.replace(containerId, frag)
        ft.setPrimaryNavigationFragment(frag)
    
        @IdRes val destId = destination.id
        val initialNavigation = backStack.isEmpty()
        // TODO Build first class singleTop behavior for fragments
        val isSingleTopReplacement = (navOptions != null && !initialNavigation
                && navOptions.shouldLaunchSingleTop()
                && backStack.peekLast()?.toInt() == destId)
    
        val isAdded: Boolean
        isAdded = if (initialNavigation) {
            true
        } else if (isSingleTopReplacement) { // Single Top means we only want one 
    instance on the back stack
            if (backStack.size > 1) { // If the Fragment to be replaced is on the FragmentManager's
    // back stack, a simple replace() isn't enough so we
    // remove it from the back stack and put our replacement
    // on the back stack in its place
                manager.popBackStack(
                        generateBackStackName(backStack.size, backStack.peekLast()!!.toInt()),
                        FragmentManager.POP_BACK_STACK_INCLUSIVE)
                ft.addToBackStack(generateBackStackName(backStack.size, destId))
            }
            false
        } else {
            ft.addToBackStack(generateBackStackName(backStack.size + 1, destId))
            true
        }
        if (navigatorExtras is Extras) {
            for ((key, value) in navigatorExtras.sharedElements) {
                ft.addSharedElement(key!!, value!!)
            }
        }
        ft.setReorderingAllowed(true)
        ft.commit()
        // The commit succeeded, update our view of the world
        return if (isAdded) {
            backStack.add(Integer(destId))
            destination
        } else {
            null
        }
    }
    
    private fun setAnimations(navOptions: NavOptions, transaction: FragmentTransaction) {
        transaction.setCustomAnimations(
                navOptions.enterAnim.takeIf { it != -1 } ?: android.R.anim.fade_in,
                navOptions.exitAnim.takeIf { it != -1 } ?: android.R.anim.fade_out,
                navOptions.popEnterAnim.takeIf { it != -1 } ?: android.R.anim.fade_in,
                navOptions.popExitAnim.takeIf { it != -1 } ?: android.R.anim.fade_out
        )
    }
    
    private fun generateBackStackName(backStackIndex: Int, destId: Int): String? {
        return "$backStackIndex-$destId"
    }
    }
    

    In the next step we have to add the navigator to NavController. Here is an example how to set it:

     override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val navHostFragment = supportFragmentManager.findFragmentById(R.id.fragmentContainer)!!
        with (findNavController(R.id.fragmentContainer)) {
            navigatorProvider += MyAwesomeFragmentNavigator(this@BaseContainerActivity, navHostFragment.childFragmentManager, R.id.fragmentContainer)
            setGraph(navGraphId)
        }
    }
    

    And nothing special in xml :)

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    
    <fragment
        android:id="@+id/fragmentContainer"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true" />
    </LinearLayout>
    

    Now each fragment from graph will have alpha transitions

    0 讨论(0)
提交回复
热议问题