Android导航组件弹出过渡问题

jay*_*dia 12 android android-navigation android-architecture-components android-architecture-navigation

我有2个动作

动作1

 <action
        android:id="@+id/actionBaseFragmentToAskForLocation"
        app:destination="@+id/introAskForLocationFragment"
        app:enterAnim="@anim/slide_in_right"
        app:exitAnim="@anim/slide_out_left"
        app:popEnterAnim="@anim/slide_in_left"
        app:popExitAnim="@anim/slide_out_right" />
Run Code Online (Sandbox Code Playgroud)

动作2

<action
        android:id="@+id/actionIntroAskLocationToLogin"
        app:destination="@id/loginFragment"
        app:enterAnim="@anim/slide_in_right"
        app:exitAnim="@anim/slide_out_left"
        app:popEnterAnim="@anim/slide_in_right"
        app:popExitAnim="@anim/fade_out"
        app:popUpTo="@+id/app_main_navigation" />
Run Code Online (Sandbox Code Playgroud)

我想要的是当第二个动作被触发时,我想清除后堆栈并仅将loginFragment设置为保留在堆栈中。

唯一的问题是当我执行Action2时,将“ slide_out_right”作为退出动画执行

我知道,如果我们从堆栈中弹出片段,则将触发action1的“ popExitAnim”,而不是action2的“ exitAnim”。

但是我想知道如何让片段执行slide_out_left动画以退出并同时将其弹出堆栈。

max*_*oin 7

我最终覆盖onCreateAnimation了调用的片段navigate。此示例显示如何通过ID导航嵌套的导航图并有条件地替换pop退出动画(或popExitAnim)。

override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
    val navController = findNavController()
    val graph = navController.graph.findNode(R.id.onboardingGraph) as NavGraph
    val dest = graph.findNode(R.id.confirmationFragment)
    if (!enter && dest != null && navController.currentDestination?.id == dest.id) {
        return AnimationUtils.loadAnimation(requireContext(), R.anim.slide_out_left)
    }
    return super.onCreateAnimation(transit, enter, nextAnim)
}
Run Code Online (Sandbox Code Playgroud)

请注意,这种特殊情况部分是由于幻灯片动画的方向性。


Jon*_*tan 3

这是一个很难解决的问题,因为它NavOptions是通过将抽屉绑定到导航图时使用的便捷方法在内部处理的。我最初使用设置菜单测试了这个解决方案,onOptionsItemSelected但基本思想在这里也应该适用。

首先,确保您的菜单项 ID 与导航片段的 ID 相对应:

<menu xmlns:android="http://schemas.android.com/apk/res/android">

    ...

    <item android:id="@+id/example_id" ... />
</menu>
Run Code Online (Sandbox Code Playgroud)
<navigation xmlns:android="http://schemas.android.com/apk/res/android" ... >

    ...

    <fragment android:id="@+id/example_id" ... />
</navigation>
Run Code Online (Sandbox Code Playgroud)

现在,不要使用现成的方法将抽屉连接到 NavController,NavigationView.OnNavigationItemSelectedListener而是在 NavHost 活动中实现并重写该方法,onNavigationItemSelected如下所示:

@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
    NavHost navHost = Navigation.findNavController(this, R.id.your_nav_host_fragment);
    return NavigationUI.onNavDestinationSelected(item, navHost);
}
Run Code Online (Sandbox Code Playgroud)

这会将选择转发为图表中的导航。替换your_nav_host_fragment为您设置的片段 ID app:defaultNavHost="true"

您会注意到,虽然这有效,但它仍然默认为幻灯片动画。这是因为该调用在内部使用以下设置NavigationUI创建了自己的调用:NavOptions

NavOptions.Builder builder = new NavOptions.Builder()
                .setLaunchSingleTop(true)
                .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);
Run Code Online (Sandbox Code Playgroud)

不幸的是,该方法尚未将 aNavOptions.Builder作为参数,但您可以基于 Android 源代码创建一个实用程序类来模拟该功能:

public class NavigationUIHelper {
    public static boolean onNavDestinationSelected(@NonNull MenuItem item,
                                                   @NonNull NavController navController,
                                                   @NonNull NavOptions.Builder builder) {
        if ((item.getOrder() & Menu.CATEGORY_SECONDARY) == 0) {
            NavDestination destination = findStartDestination(navController.getGraph());
            builder.setPopUpTo(destination.getId(), false);
        }
        NavOptions options = builder.build();
        try {
            navController.navigate(item.getItemId(), null, options);
            return true;
        } catch (IllegalArgumentException e) {
            return false;
        }
    }

    // Need to copy this private method as well
    private static NavDestination findStartDestination(@NonNull NavGraph graph) {
        NavDestination startDestination = graph;
        while (startDestination instanceof NavGraph) {
            NavGraph parent = (NavGraph) startDestination;
            startDestination = parent.findNode(parent.getStartDestination());
        }
        return startDestination;
    }
}
Run Code Online (Sandbox Code Playgroud)

最后,在您的 Activity 中,您现在可以将对 NavigationUI 的调用替换为在 NavigationUIHelper 中实现的调用:

@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
    NavHost navHost = Navigation.findNavController(this, R.id.your_nav_host_fragment);
    NavOptions.Builder builder = new NavOptions.Builder()
                .setLaunchSingleTop(true)
                .setEnterAnim(R.anim.custom_enter)
                .setExitAnim(R.anim.custom_exit)
                .setPopEnterAnim(R.anim.custom_pop_enter)
                .setPopExitAnim(R.anim.custom_pop_exit);
    return NavigationUIHelper.onNavDestinationSelected(item, navHost, builder);
}
Run Code Online (Sandbox Code Playgroud)

这应该允许您根据自己的喜好更改抽屉过渡动画,而无需替换导航组件。