导航 popUpTo 和 PopUpToInclusive 不会清除后台堆栈
Navigation popUpTo and PopUpToInclusive aren't clearing the backstack
我是 Android Jetpack Navigation 架构的新手。我正在尝试一个新的应用程序。有一个 activity 和一些片段,其中两个是登录屏幕和电子邮件登录屏幕。我在导航 XML 中定义了这些片段。 App的流程如下:
Login screen
→Email Login screen
我想要的是,在导航到电子邮件登录屏幕后,当我按返回键时,应用程序退出。这意味着登录屏幕的后台堆栈已删除。我知道登录屏幕不应该那样工作,但我还在想办法。
我遵循了 Google 的 Get started with the Navigation component 的文档。它说,使用 app:popUpTo
和 app: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:popUpTo
和app: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()
}
}
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,依此类推。考虑以下两个操作:
- 一个动作E->F我们写:
<action
...
app:destination="@+id/F"
app:popUpTo="@+id/C"
app:popUpToInclusive="false"/>
- 对于 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->G
的 popUpTo
中定义)之间的所有片段都将被删除,但这次片段 B 也将被删除,因为在 F->G 动作中我们写了app:popUpToInclusive="true"
。所以返回堆栈变为:
A->G(G现在在最上面)
我是 Android Jetpack Navigation 架构的新手。我正在尝试一个新的应用程序。有一个 activity 和一些片段,其中两个是登录屏幕和电子邮件登录屏幕。我在导航 XML 中定义了这些片段。 App的流程如下:
Login screen
→Email Login screen
我想要的是,在导航到电子邮件登录屏幕后,当我按返回键时,应用程序退出。这意味着登录屏幕的后台堆栈已删除。我知道登录屏幕不应该那样工作,但我还在想办法。
我遵循了 Google 的 Get started with the Navigation component 的文档。它说,使用 app:popUpTo
和 app: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:popUpTo
和app: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 后被销毁。
您可以像
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()
}
}
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,依此类推。考虑以下两个操作:
- 一个动作E->F我们写:
<action
...
app:destination="@+id/F"
app:popUpTo="@+id/C"
app:popUpToInclusive="false"/>
- 对于 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->G
的 popUpTo
中定义)之间的所有片段都将被删除,但这次片段 B 也将被删除,因为在 F->G 动作中我们写了app:popUpToInclusive="true"
。所以返回堆栈变为:
A->G(G现在在最上面)