如何为导航操作定义默认动画?

Sme*_*ard 28 android android-animation android-jetpack android-architecture-navigation

我正在使用Android Studio 3.2 Canary 14和导航架构组件.使用此功能,您可以像使用Intent一样定义过渡动画.

但是动画被设置为导航图中动作的属性,如下所示:

<fragment
    android:id="@+id/startScreenFragment"
    android:name="com.example.startScreen.StartScreenFragment"
    android:label="fragment_start_screen"
    tools:layout="@layout/fragment_start_screen" >
  <action
    android:id="@+id/action_startScreenFragment_to_findAddressFragment"
    app:destination="@id/findAddressFragment"
    app:enterAnim="@animator/slide_in_right"
    app:exitAnim="@animator/slide_out_left"
    app:popEnterAnim="@animator/slide_in_left"
    app:popExitAnim="@animator/slide_out_right"/>
</fragment>
Run Code Online (Sandbox Code Playgroud)

为图表中的所有操作定义这一过程非常繁琐!

有没有办法在动作上将一组动画定义为默认值?

我没有运气使用款式.

Mar*_*ler 11

R.anim定义了默认动画(作为final):

  • nav_default_enter_anim

  • nav_default_exit_anim

  • nav_default_pop_enter_anim

  • nav_default_pop_exit_anim

要更改此行为,您必须使用自定义NavOptions,

因为这是将这些动画分配给NavAction的地方.

可以使用NavOptions.Builder分配这些:

protected NavOptions getNavOptions() {

    NavOptions navOptions = 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;
}
Run Code Online (Sandbox Code Playgroud)

最有可能需要创建一个DefaultNavFragment,扩展类androidx.navigation.fragment(那里的文档似乎还没有完成).

或者...当看到attrs.xml那个包裹时; 这些动画都是风格化的:

<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>
Run Code Online (Sandbox Code Playgroud)

这意味着,可以定义相应的样式 - 并定义这些样式,作为主题的一部分......

可以在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>
Run Code Online (Sandbox Code Playgroud)

  • 请注意,这将覆盖 XML 中声明的 NavOptions,这意味着您将丢失 singleTop、popUpTo 和默认参数等属性。 (4认同)
  • 这不适用于 `parent="Theme.MaterialComponents..."` 我猜他们仍然需要将这些添加到 MaterialComponents 样式中。 (3认同)
  • 您可以发布一个示例样式作为主题应用吗?我尝试了你的第二种方法,但它不起作用。 (2认同)
  • NavAction_enterAnim导致生成错误,错误:找不到样式属性'attr / NavAction_enterAnim(aka com.myapp.myapp:attr / NavAction_enterAnim)'。 (2认同)
  • 材料主题或本示例中列出的主题似乎对我不起作用 (2认同)
  • 此方法不应该用作 util,因为它将替换与 NavOptions 相关的 XML 属性,例如 @RisalFajarAmiyardi 告诉的 singleTop、popUpTo、popUpToInclusive。我在这个问题上浪费了2个小时 (2认同)

fad*_*a21 6

我找到了需要扩展NavHostFragment. 它类似于Link182,但较少涉及代​​码。大多数情况下,它需要从标准更改所有 xml defaultNavHost 片段名称:

<fragment
    app:defaultNavHost="true"
    ...
    android:name="androidx.navigation.fragment.NavHostFragment"

Run Code Online (Sandbox Code Playgroud)

到:

<fragment
    app:defaultNavHost="true"
    ...
    android:name="your.app.package.fragments.NavHostFragmentWithDefaultAnimations"
Run Code Online (Sandbox Code Playgroud)

代码NavHostFragmentWithDefaultAnimations

package your.app.package.fragments

import android.content.Context
import android.os.Bundle
import androidx.fragment.app.FragmentManager
import androidx.navigation.*
import androidx.navigation.fragment.FragmentNavigator
import androidx.navigation.fragment.NavHostFragment
import your.app.package.R

// Those are navigation-ui (androidx.navigation.ui) defaults
// used in NavigationUI for NavigationView and BottomNavigationView.
// Set yours here
private val defaultNavOptions = navOptions {
    anim {
        enter = R.animator.nav_default_enter_anim
        exit = R.animator.nav_default_exit_anim
        popEnter = R.animator.nav_default_pop_enter_anim
        popExit = R.animator.nav_default_pop_exit_anim
    }
}

private val emptyNavOptions = navOptions {}

class NavHostFragmentWithDefaultAnimations : NavHostFragment() {

    override fun onCreateNavController(navController: NavController) {
        super.onCreateNavController(navController)
        navController.navigatorProvider.addNavigator(
            // this replaces FragmentNavigator
            FragmentNavigatorWithDefaultAnimations(requireContext(), childFragmentManager, id)
        )
    }

}

/**
 * Needs to replace FragmentNavigator and replacing is done with name in annotation.
 * Navigation method will use defaults for fragments transitions animations.
 */
@Navigator.Name("fragment")
class FragmentNavigatorWithDefaultAnimations(
    context: Context,
    manager: FragmentManager,
    containerId: Int
) : FragmentNavigator(context, manager, containerId) {

    override fun navigate(
        destination: Destination,
        args: Bundle?,
        navOptions: NavOptions?,
        navigatorExtras: Navigator.Extras?
    ): NavDestination? {
        // this will try to fill in empty animations with defaults when no shared element transitions are set
        // https://developer.android.com/guide/navigation/navigation-animate-transitions#shared-element
        val shouldUseTransitionsInstead = navigatorExtras != null
        val navOptions = if (shouldUseTransitionsInstead) navOptions
        else navOptions.fillEmptyAnimationsWithDefaults()
        return super.navigate(destination, args, navOptions, navigatorExtras)
    }

    private fun NavOptions?.fillEmptyAnimationsWithDefaults(): NavOptions =
        this?.copyNavOptionsWithDefaultAnimations() ?: defaultNavOptions

    private fun NavOptions.copyNavOptionsWithDefaultAnimations(): NavOptions =
        let { originalNavOptions ->
            navOptions {
                launchSingleTop = originalNavOptions.shouldLaunchSingleTop()
                popUpTo(originalNavOptions.popUpTo) {
                    inclusive = originalNavOptions.isPopUpToInclusive
                }
                anim {
                    enter =
                        if (originalNavOptions.enterAnim == emptyNavOptions.enterAnim) defaultNavOptions.enterAnim
                        else originalNavOptions.enterAnim
                    exit =
                        if (originalNavOptions.exitAnim == emptyNavOptions.exitAnim) defaultNavOptions.exitAnim
                        else originalNavOptions.exitAnim
                    popEnter =
                        if (originalNavOptions.popEnterAnim == emptyNavOptions.popEnterAnim) defaultNavOptions.popEnterAnim
                        else originalNavOptions.popEnterAnim
                    popExit =
                        if (originalNavOptions.popExitAnim == emptyNavOptions.popExitAnim) defaultNavOptions.popExitAnim
                        else originalNavOptions.popExitAnim
                }
            }
        }

}
Run Code Online (Sandbox Code Playgroud)

您可以通过传递 navOptions 在导航图 xml 或代码中更改动画。要禁用默认动画,请传递动画值为 0 的 navOptions 或传递 navigatorExtras(设置共享转换)。

测试版本:

implementation "androidx.navigation:navigation-fragment-ktx:2.3.1"
implementation "androidx.navigation:navigation-ui-ktx:2.3.1"
Run Code Online (Sandbox Code Playgroud)

  • 这很棒!如果您用它创建一个小型库并将其发布到 jitpack.io 上,那就太酷了 (3认同)
  • 我的意思是,这样的事情从内心深处伤害了我。不要误会我的意思,这是一个实用的解决方案,但它只是证明了 Navigation Ui 原作者的无能,他们以这种方式设计了原始库,因此对于像全局动画这样的琐碎事情,我们必须进行这种级别的定制。 (3认同)
  • 这应该是公认的答案 (2认同)