导航 popUpTo 和 PopUpToInclusive 不会清除后台堆栈

Navigation popUpTo and PopUpToInclusive aren't clearing the backstack

我是 Android Jetpack Navigation 架构的新手。我正在尝试一个新的应用程序。有一个 activity 和一些片段,其中两个是登录屏幕和电子邮件登录屏幕。我在导航 XML 中定义了这些片段。 App的流程如下:

Login screenEmail Login screen

我想要的是,在导航到电子邮件登录屏幕后,当我按返回键时,应用程序退出。这意味着登录屏幕的后台堆栈已删除。我知道登录屏幕不应该那样工作,但我还在想办法。

我遵循了 Google 的 Get started with the Navigation component 的文档。它说,使用 app:popUpToapp:popUpToInclusive="true" 应该清除后台堆栈,但是当我在电子邮件登录屏幕上按回退时,它仍然返回登录而不是退出。

所以,这就是我尝试过的方法。

nav_main.xml

<fragment android:id="@+id/loginFragment"
          android:name="com.example.myapp.ui.main.LoginFragment"
          android:label="@string/login"
          tools:layout="@layout/fragment_login" >
    
    <action
        android:id="@+id/action_login_to_emailLoginFragment"
        app:destination="@id/emailLoginFragment"
        app:popEnterAnim="@anim/slide_in_right"
        app:popExitAnim="@anim/slide_out_right"
        app:popUpTo="@+id/emailLoginFragment"
        app:popUpToInclusive="true"/>

</fragment>

<fragment android:id="@+id/emailLoginFragment"
          android:name="com.example.myapp.ui.main.EmailLoginFragment"
          android:label="EmailLoginFragment"
          tools:layout="@layout/fragment_login_email" />

LoginFragment.kt

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View {
    binding.emailLoginButton.setOnClickListener {
        findNavController().navigate(R.id.action_login_to_emailLoginFragment)
    }
    
    return binding.root
}

我给了一个按钮点击事件。在其中,我使用导航控制器通过为其提供操作 ID 来导航到电子邮件登录屏幕。 <action>中有app:popUpToapp:popUpToInclusive="true".

在一遍又一遍地阅读文档以及阅读大量 Whosebug 问题后,我发现这些属性应该将我的登录屏幕从后台堆栈中删除。但他们没有。该按钮确实导航到电子邮件登录屏幕,但是当我按下返回时,它仍然返回到登录屏幕而不是退出应用程序。我错过了什么?

<action
        android:id="@+id/action_login_to_emailLoginFragment"
        app:destination="@id/emailLoginFragment"
        app:popEnterAnim="@anim/slide_in_right"
        app:popExitAnim="@anim/slide_out_right"
        app:popUpTo="@+id/loginFragment"
        app:popUpToInclusive="true"/>

您的 popUpTo 将返回到电子邮件登录,然后由于包容性而将其弹出。 如果您将 popUpTo 更改为您的登录片段,它将被导航回并弹出,因为包含标志,这将导致您期望的行为。

这两行使技巧起作用:

如果您想从 A 到 B 并希望完成 A:

您需要通过此操作调用 B:

    <fragment
        android:id="@+id/fragmentA"            
        tools:layout="@layout/fragment_a">

        <action
            android:id="@+id/action_call_B"
            app:destination="@+id/fragmentB"
            app:popUpTo="@id/fragmentA"
            app:popUpToInclusive="true" />

    </fragment>

    <fragment
        android:id="@+id/fragmentB"
        tools:layout="@layout/fragment_b">


    </fragment>

如果您将日志记录到您的片段中,您可以看到片段 A 在使用此操作调用片段 B 后被销毁。

您可以像 一样在 XML 中执行此操作,或者您也可以通过编程方式执行此操作:

NavOptions navOptions = new NavOptions.Builder().setPopUpTo(R.id.loginRegister, true).build();
Navigation.findNavController(mBinding.titleLogin).navigate(R.id.login_to_main, null, navOptions);

假设您的应用有三个目的地——A、B 和 C——以及从 A 到 B、B 到 C、C 回到 A 的操作。相应的导航图如图

对于每个导航操作,都会将一个目的地添加到返回堆栈中。如果您要通过此流程重复导航,则您的返回堆栈将包含每个目的地的多组(A、B、C、A、B、C、A 等)。为避免这种重复,您可以在将您从目的地 C 带到目的地 A 的操作中指定 app:popUpTo 和 app:popUpToInclusive,如下例所示:

<fragment
android:id="@+id/c"
android:name="com.example.myapplication.C"
android:label="fragment_c"
tools:layout="@layout/fragment_c">

<action
    android:id="@+id/action_c_to_a"
    app:destination="@id/a"
    app:popUpTo="@+id/a"
    app:popUpToInclusive="true"/>

到达目的地 C 后,返回堆栈包含每个目的地(A、B、C)的一个实例。当导航回目的地 A 时,我们也 popUpTo A,这意味着我们在导航时从堆栈中移除 B 和 C。使用 app:popUpToInclusive="true",我们还将第一个 A 从堆栈中弹出,有效地清除它。请注意,如果您不使用 app:popUpToInclusive,您的返回堆栈将包含目标 A

的两个实例

popUpTo 定义你按回车键后要去的地方。如果您设置 popUpInclusive = true,导航也会跳过该位置(在 popUpTo 中)。

样本:A -> B -> A

FragmentB.kt

尝试弹出控制器的返回堆栈

private fun popBackStackToA() {
    if (!findNavController().popBackStack()) {
//            Call finish on your Activity
        requireActivity().finish()
    }
}

Back Stack

I write this answer for people who have not completely understood the way popUpTo works and I hope its example helps someone because most examples for navigation are repetitive in most sites and do not show the whole picture.

在任何<action>中,如果我们为app:popUpTo写一个值,这意味着我们想在完成操作后立即从返回堆栈中删除一些片段,但是哪些片段将要删除动作完成后从返回堆栈中移除?

它的顺序是后进先出所以:

  • 最后一个片段和 popUpTo 中定义的片段之间的所有片段都将被删除。
  • 如果我们添加app:popUpToInclusive="true",那么片段定义 popUpTo 中的也将被删除。

示例: 考虑这样的导航图中从 A 到 G 的片段:

A->B->C->D->E->F->G

我们可以从 A 到 B,然后从 B 到 C,依此类推。考虑以下两个操作:

  1. 一个动作E->F我们写:
<action
    ...
    app:destination="@+id/F"
    app:popUpTo="@+id/C"
    app:popUpToInclusive="false"/>
  1. 对于 F->G 我们写:
<action
    ...
    app:destination="@+id/G"
    app:popUpTo="@+id/B"
    app:popUpToInclusive="true"/>

然后在使用操作 E->F 从 E 到 F 之后,最后一个片段 (F) 和 C(在 [=12= 中定义)之间的片段] 的 E->F) 将被删除。由于 app:popUpToInclusive="false" 这次不会删除片段 C,所以我们的返回堆栈变为:

A->B->C->F(F 当前在顶部)

现在,如果我们使用操作 F->G 转到片段 G: 最后一个片段(G)和 B(在 F->GpopUpTo 中定义)之间的所有片段都将被删除,但这次片段 B 也将被删除,因为在 F->G 动作中我们写了app:popUpToInclusive="true"。所以返回堆栈变为:

A->G(G现在在最上面)