76

基本上,我有以下导航图:

在此处输入图像描述

我想在到达导航图中的起点fragment 2后将其更改为右侧(以防止fragment 1在按下后退按钮时返回 - 就像启动屏幕一样)。

这是我的代码:

navGraph = navController.getGraph();
navGraph.setStartDestination(R.id.fragment2);
navController.setGraph(navGraph);

但是,显然它不起作用,fragment 1按下后退按钮后它又恢复了。

我做错了吗?还有其他解决方案吗?

4

8 回答 8

86

更新:

当你有这样的导航图时:

<fragment
    android:id="@+id/firstFragment"
    android:name="com.appname.package.FirstFragment" >
    <action
        android:id="@+id/action_firstFragment_to_secondFragment"
        app:destination="@id/secondFragment" /> 
</fragment>

<fragment
    android:id="@+id/secondFragment"
    android:name="com.appname.package.SecondFragment"/>

并且您想导航到第二个片段并使其成为图表的根,请指定下一个NavOptions

NavOptions navOptions = new NavOptions.Builder()
        .setPopUpTo(R.id.firstFragment, true)
        .build();

并将它们用于导航:

Navigation.findNavController(view).navigate(R.id.action_firstFragment_to_secondFragment, bundle, navOptions);

setPopUpTo(int destinationId, boolean inclusive)- 在导航之前弹出到给定的目的地。这会从返回堆栈中弹出所有不匹配的目标,直到找到该目标。

destinationId- 要弹出的目的地,清除所有干预目的地。

inclusive- true 也从返回堆栈中弹出给定的目的地。


选择:

<fragment
    android:id="@+id/firstFragment"
    android:name="com.appname.package.FirstFragment" >
<action
    android:id="@+id/action_firstFragment_to_secondFragment"
    app:destination="@id/secondFragment"
    app:popUpTo="@+id/firstFragment"
    app:popUpToInclusive="true" /> 
</fragment>

<fragment
    android:id="@+id/secondFragment"
    android:name="com.appname.package.SecondFragment"/>

然后在您的代码上:

findNavController(fragment).navigate(
    FirstFragmentDirections.actionFirstFragmentToSecondFragment())

旧答案

已弃用:操作clearTask属性和相关的 APINavOptions已被弃用。

来源:https ://developer.android.com/jetpack/docs/release-notes


如果您想将根片段更改为fragment 2(例如,按下后退按钮后fragment 2您将退出应用程序),您应该将下一个属性放入您的actionor destination

app:clearTask="true"

实际上,它看起来像下一个方式:

<fragment
    android:id="@+id/firstFragment"
    android:name="com.appname.package.FirstFragment"
    android:label="fragment_first" >
    <action
        android:id="@+id/action_firstFragment_to_secondFragment"
        app:destination="@id/secondFragment"
        app:clearTask="true" /> 
</fragment>

<fragment
    android:id="@+id/secondFragment"
    android:name="com.appname.package.SecondFragment"
    android:label="fragment_second"/>

我已经加入app:clearTask="true"行动。


现在,当您执行导航fragment 1fragment 2使用下一个代码时:

Navigation.findNavController(view)
        .navigate(R.id.action_firstFragment_to_secondFragment);
于 2018-07-04T12:49:58.963 回答
18

我找到了一个解决方案,但它很难看。我想这是一个 alpha 库所期望的,但我希望 Google 考虑简化/修复它,因为这是一种非常流行的导航模式。

Alexey 的解决方案对我不起作用。我的问题是我的操作栏上显示了向上箭头,方法是:

NavigationUI.setupActionBarWithNavController(this, navController)

如果我按照 Alexey 上面的建议进行操作,我的新起始片段仍然有一个箭头指向我的初始起始片段。如果我按下那个向上箭头,我的应用程序会重新启动,过渡到自身(新的开始片段)

这是获得我想要的代码所需的代码:

  • 片段 #1 是我的应用程序最初启动的地方
  • 我可以在片段#1 中进行身份验证检查,然后以编程方式将开始更改为片段#2。
  • 一旦进入 Fragment #2,就没有向上箭头,并且按下后退按钮不会将您带到 Fragment #1。

这是完成此操作的代码。在我的 Activity 的 onCreate 中:

// Setup the toolbar
val toolbar = findViewById<Toolbar>(R.id.toolbar)
setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(false)

// Configure the navigation
val navHost = nav_host_fragment as NavHostFragment
graph = navHost.navController
        .navInflater.inflate(R.navigation.nav_graph)
graph.startDestination = R.id.welcomeFragment

// This seems to be a magical command. Not sure why it's needed :(
navHost.navController.graph = graph

NavigationUI.setupActionBarWithNavController(this, navHost.navController)

并且:

fun makeHomeStart(){
    graph.startDestination = R.id.homeFragment
}

然后根据 Alexey 的建议,在 Fragment #1 的 onActivityCreated 中:

override fun onActivityCreated(savedInstanceState: Bundle?) {
    ...

    // Check for user authentication
    if(sharedViewModel.isUserAuthenticated()) {

        (activity as MainActivity).makeHomeStart() //<---- THIS is the key
        val navOptions = NavOptions.Builder()
                .setPopUpTo(R.id.welcomeFragment, true)
                .build()
        navController.navigate(R.id.action_welcomeFragment_to_homeFragment,null,navOptions)

    } else {
        navController.navigate(R.id.action_welcomeFragment_to_loginFragment)
    }
}

关键代码是: (activity as MainActivity).makeHomeStart()它只是在活动中运行一个方法来更改图表 startDestination。我可以清理它并将其变成一个界面,但我会等待谷歌并希望他们改进整个过程。对我来说,方法“setPopUpTo”的名称似乎很糟糕,而且您命名从图表中删除的片段并不直观。他们在 navOptions 中进行这些更改对我来说也很奇怪。我认为 navOptions 只会与它们连接的导航操作有关。

而且我什至不知道是什么navHost.navController.graph = graph,但是没有它,向上的箭头会返回。:(

我正在使用导航 1.0.0-alpha06。

于 2018-10-27T00:00:07.117 回答
16

在 MainActivity.kt

    val navHostFragment = supportFragmentManager.findFragmentById(R.id.nav_host_fragment) as NavHostFragment
    val inflater = navHostFragment.navController.navInflater
    val graph = inflater.inflate(R.navigation.booking_navigation)

    if (isTrue){
        graph.startDestination = R.id.DetailsFragment
    }else {
        graph.startDestination = R.id.OtherDetailsFragment
    }

    val navController = navHostFragment.navController
    navController.setGraph(graph, intent.extras)

从 nav_graph.xml 中删除 startDestination

?xml version="1.0" encoding="utf-8"?>
<!-- app:startDestination="@id/oneFragment" -->

<navigation xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  xmlns:tools="http://schemas.android.com/tools"
  android:id="@+id/navigation_main">

  <fragment
    android:id="@+id/DetailFragment"
    android:name="DetailFragment"
    android:label="fragment_detail"
    tools:layout="@layout/fragment_detail"/>
  <fragment
    android:id="@+id/OtherDetailFragment"
    android:name="OtherDetailFragment"
    android:label="fragment_other_detail"
    tools:layout="@layout/fragment_other_detail"/>
</navigation>
于 2020-07-17T06:06:30.963 回答
14

您也可以尝试以下方法。

val navController = findNavController(R.id.nav_host_fragment)
if (condition) {
    navController.setGraph(R.navigation.nav_graph_first)
} else {
    navController.setGraph(R.navigation.nav_graph_second)
}

与其尝试弹出起始目的地或手动导航到目标目的地,不如拥有另一个具有不同工作流程的导航图。当您有条件地想要完全不同的导航流程时,这会更好。

于 2019-12-12T16:48:54.483 回答
3

你真的不需要弹出 Splash Fragment。它可以在您的应用程序生命的其余部分保留在那里。您应该做的是从启动屏幕确定下一个要显示的屏幕。

登录状态机流程

在上图中,您可以在 Splash Screen State 中询问是否有已保存的 LoginToken。如果为空,则导航到登录屏幕。

登录屏幕完成后,您分析结果保存令牌并导航到您的下一个片段主屏幕。

在主屏幕中按下返回按钮时,您将向启动屏幕发回一条结果消息,指示它完成应用程序。

波纹管代码可能会有所帮助:

val nextDestination = if (loginSuccess) {
    R.id.action_Dashboard
} else {
    R.id.action_NotAuthorized
}

val options = NavOptions.Builder()
    .setPopUpTo(R.id.loginParentFragment, true)
    .build()

findNavController().navigate(nextDestination, null, options)
于 2019-10-18T16:19:02.020 回答
3

对于那些拥有与此类似内容的导航 xml 文件的人:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mobile_navigation"
    app:startDestination="@+id/nav_home">

    <fragment
        android:id="@+id/nav_home"
        android:name="HomeFragment"
        android:label="@string/menu_home"
        tools:layout="@layout/fragment_home" />

    <fragment
        android:id="@+id/nav_users"
        android:name="UsersFragment"
        android:label="@string/users"
        tools:layout="@layout/fragment_users" />

    <fragment
        android:id="@+id/nav_settings"
        android:name="SettingsFragment"
        android:label="@string/settings"
        tools:layout="@layout/fragment_settings" />
</navigation>

假设当前打开的片段是主片段,并且您想要导航到用户片段,因为只需调用要从导航控制器导航到导航方法的元素的 setOnClickListener,类似于以下代码:

yourElement.setOnClickListener {
  view.findNavController().navigate(R.id.nav_users)
}

这将使应用程序导航到另一个片段,并且还将处理工具栏中的标题。

于 2020-11-01T00:52:36.417 回答
2

好的,在搞砸了一段时间后,我找到了一个不需要大量工作的对我有用的解决方案。

看起来必须有两件事才能使其发挥作用,就好像您的 secondFragment 是您的起始目的地一样。

在接受的帖子中使用 ALTERNATIVE 选项

<fragment
    android:id="@+id/firstFragment"
    android:name="com.appname.package.FirstFragment" >

    <action
        android:id="@+id/action_firstFragment_to_secondFragment"
        app:destination="@id/secondFragment"
        app:popUpTo="@+id/firstFragment"
        app:popUpToInclusive="true" /> 

</fragment>

<fragment
    android:id="@+id/secondFragment"
    android:name="com.appname.package.SecondFragment"/>

以上将从堆栈中删除 firstFragment 并在移动时膨胀 secondFragment。该应用程序无法再退回到 firstFragment,但是您的左侧与 secondFragment 一样显示了@szaske 所述的后退箭头。

这就是造成差异的原因。我之前使用 NavigationController.graph 像这样定义了我的 AppBarConfig

// Old code
val controller by lazy { findNavController(R.id.nav_host_fragment) }
val appBarConfig by lazy { AppBarConfiguration(controller.graph) }

更新它以定义一组顶级目的地纠正了在 secondFragment 上显示后退箭头而不是汉堡菜单图标的问题。

// secondFragment will now show hamburger menu instead of back arrow.
val appBarConfig by lazy { AppBarConfiguration(setOf(R.id.firstFragment, R.id.secondFragment)) }

设置起始目的地可能会对您的项目产生负面影响,也可能不会产生负面影响,因此请根据需要进行设置,但在此示例中我们不需要这样做。如果它让您感到温暖和模糊以确保您的图表定义了正确的起始片段,您可以这样做。

controller.graph.startDestination = R.id.secondFragment

注意:设置此项并不能阻止在 secondFragment 中出现后退箭头,并且我发现似乎对导航没有影响。

于 2020-08-21T12:19:55.083 回答
0

我试图startDestination通过代码修改。它看起来运行良好,但在不保留活动中,导航组件不会恢复片段堆栈。

我用假人解决了这个问题startDestination

  • startDestination 是 EmptyFragment(只是一个假人)
  • EmptyFragment 到 FirstFragment 动作需要popUpTo=EmptyFragmentpopUpToInclusive=true

导航图图像

Activity.onCreate()

    if (savedInstanceState == null) {
        val navHost = supportFragmentManager.findFragmentById(R.id.nav_host_fragment)!!
        val navController = navHost.findNavController()
        if (loginComplete) {
            navController.navigate(
                R.id.action_emptyFragment_to_FirstFragment
            )
        } else {
            navController.navigate(
                R.id.action_emptyFragment_to_WelcomeFragment
            )
        }
    }

重新创建 Activity 时,savedInstanceState 不为 null 并自动恢复片段。

于 2021-12-10T02:03:01.750 回答