导航组件:跳转到动态根目录并清除后退堆栈

时间:2020-03-06 11:57:21

标签: android kotlin android-jetpack android-navigation android-jetpack-navigation

使用Jetpack导航时,我们可以使用popUpTopopInclusive清除堆栈。但是当我不知道popUpto的目的地时如何清除堆栈?

示例

说我有3个主要目的地,到达目的地时应该有一个清晰的栈。每个主要目的地都有自己的屏幕流(可以通过深度链接直接访问)。

假设导航在此处向下流动:

Start app:
 - Main Dest 1 (nav to dest 2)
     - Dest 2 (nav to dest 3)
         - Dest 3 (nav to main dest 2)
            --clear--
 - (with clear stack)
   Main Dest 2 (nav to dest 4)
     - Dest 4 (nav to main dest 3)
         --clear-->
 - (with clear stack)
   Main Dest 3
 - Back button should close the app here

由于导航是动态的,因此我不能保证根目录。例如,即使我可以弹出“主要目标2”,我也不知道它在堆栈中,因为我可能直接从URL转到了“目标4”。

我有办法知道哪个目的地是最低的,所以我可以弹出它并清除堆栈?

4 个答案:

答案 0 :(得分:1)

我找不到解决该问题的任何官方解决方案,但这是一种对我有用的方法:

private val mBackStackField by lazy {
  val field = NavController::class.java.getDeclaredField("mBackStack")
  field.isAccessible = true
  field
}

fun popToRoot(navController: NavController) {
  val arrayDeque = mBackStackField.get(navController) as java.util.ArrayDeque<NavBackStackEntry>
  val graph = arrayDeque.first.destination as NavGraph
  val rootDestinationId = graph.startDestination

  val navOptions = NavOptions.Builder()
    .setPopUpTo(rootDestinationId, false)
    .build()

  navController.navigate(rootDestinationId, null, navOptions)
}

答案 1 :(得分:1)

图形的根始终在堆栈上,因此您可以始终popUpTo到达该目的地。

因此,只需确保您的根<navigation>元素具有关联的android:id

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/nav_graph"
    app:startDestination="@+id/main_dest_1">
    <!-- The rest of your graph -->
</navigation>

然后,只要您想弹出整个图表,只需popUpTo="@id/nav_graph"

当然,根据Principles of Navigation,当您转到Main Dest 2,Main Dest 3等时,您应该不要弹出整个堆栈。这非常重要从UX的角度来看,用户知道后退按钮何时退出应用程序-启动目标正是该路标,供用户知道何时会发生,并防止用户期望返回到第一个屏幕时意外关闭应用程序退出您的应用。

答案 2 :(得分:0)

据我所知,没有简单而优美的方法来获得您要实现的目标。 不过,有几种可能的解决方案。


使用某种基本片段

您可能有一个始终位于堆栈底部的基本目标,我们将其称为baseDest。 在需要清除堆栈的操作中,您可以始终使用

<action
    android:id="..."
    app:destination="@id/mainDestX"
    app:popUpTo="@+id/baseDest"
    app:popUpToInclusive="false" />

这将始终将baseDest保留在堆栈的底部,使您始终可以弹出它。 要在您的主要目的地之一上实现反压行为,您可以:

  1. 在所有主要目标中覆盖onBackPressed方法以关闭应用程序。 (如果您的所有主要目标都来自同一类,则可以轻松完成此操作)
  2. 当由于反压加载baseDest片段时,以编程方式关闭应用程序。

重新考虑您的应用导航

也许您应该重新考虑应用程序中导航的工作方式。 与其拥有某种可能的循环图结构,不如处理类似于树的导航结构(用户也可以更直观地理解它们)。当然,这并不总是一种选择,但是您应该牢记这一点,并考虑重新构造导航。


以编程方式找到解决方案

手动执行所有操作和导航,并使用NavController中提供的方法在导航到主要目的地之一之前手动删除堆栈中的所有条目。 为此,一种可行的方法是通过在片段中使用findNavController方法来检索导航控制器。 我尚未测试以下任何一种方法,但是您可以尝试:

  1. 执行yourNavController.getGraph().clear(),然后执行yourNavController.navigate(yourDest)
  2. 在应用程序开始加载任何片段之前,请使用saved = yourNavController.saveState(),并在导航至您的其中一个mainDestinations时使用yourNavController.restoreState(saved),然后再输入yourNavController.navigate(yourDest)
  3. 创建一个新的NavGraph并使用yourNavController.setGraph(createdGraph)

答案 3 :(得分:0)

您可以在根或基本片段中添加目标更改侦听器,并侦听导航更改。

NavController.OnDestinationChangedListener { controller, destination, arguments ->
    // react on change or 
    //you can check destination.id or destination.label and act based on tha
)}