Android 如何以编程方式更改导航图的起始目标[Jetpack]

Android 如何以编程方式更改导航图的起始目标[Jetpack],android,android-jetpack,android-navigation,android-architecture-navigation,Android,Android Jetpack,Android Navigation,Android Architecture Navigation,基本上,我有以下导航图: 我想在到达后立即将导航图中的起始点更改为片段2(以防止在按下后退按钮时返回到片段1,就像启动屏幕一样) 这是我的代码: navGraph = navController.getGraph(); navGraph.setStartDestination(R.id.fragment2); navController.setGraph(navGraph); 但是,很明显,它不工作了,在按下后退按钮后,它返回到片段1 我做错了吗? 还有其他解决方案吗?更新: 当您有如下导航

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

我想在到达后立即将导航图中的起始点更改为
片段2
(以防止在按下后退按钮时返回到
片段1
,就像启动屏幕一样)

这是我的代码:

navGraph = navController.getGraph();
navGraph.setStartDestination(R.id.fragment2);
navController.setGraph(navGraph);
但是,很明显,它不工作了,在按下后退按钮后,它返回到
片段1

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

更新: 当您有如下导航图时:

<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"/>
并将其用于导航:

Navigation.findNavController(view).navigate(R.id.action_firstFragment_to_secondFragment, bundle, navOptions);
setPoputo(int destinationId,含布尔值)
-导航前弹出到给定目标。这将从后堆栈中弹出所有不匹配的目标,直到找到该目标

destinationId
-要弹出的目的地,清除所有中间目的地

inclusive
-如果为true,则也会从后堆栈中弹出给定的目标


备选方案: 旧答案 已弃用:已弃用
NavOptions
中操作和关联API的
clearTask
属性

资料来源:


如果您想将根片段更改为
fragment 2
(例如,按下
fragment 2
上的“后退”按钮后,您将退出应用程序),您应该将下一个属性添加到
操作
目的地

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"/>

我找到了解决这个问题的办法,但很难看。我想这在alpha库中是意料之中的,但我希望谷歌能够简化/修复这一问题,因为这是一种非常流行的导航模式

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

NavigationUI.setupActionBarWithNavController(此,navController)

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

下面是实现我想要的功能所需的代码:

  • 片段#1是我的应用程序最初启动的地方
  • 我可以在片段#1中执行身份验证检查,然后通过编程将开始更改为片段#2
  • 进入片段#2后,没有向上箭头,按下后退按钮不会进入片段#1
下面是实现这一点的代码。在我的活动的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)
然后根据Alexey的建议,在片段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)
    }
}
覆盖活动创建的乐趣(savedInstanceState:Bundle?){
...
//检查用户身份验证
if(sharedViewModel.isUserAuthenticated()){

(活动作为MainActivity)。makeHomeStart()//您实际上不需要弹出启动片段。它可以在应用程序的剩余生命中一直保留在那里。您应该做的是从启动屏幕确定要显示的下一个屏幕

在上图中,您可以在初始屏幕状态下询问是否存在已保存的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)

您也可以尝试以下方法

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)
}
与其尝试弹出开始目的地或手动导航到目标目的地,不如使用另一个具有不同工作流的导航图。 如果您希望有条件地使用完全不同的导航流,则这一点会更好。

在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>
?xml version=“1.0”encoding=“utf-8”>

好吧,在处理了一段时间后,我找到了一个适合我的解决方案,它不需要大量的工作

似乎有两件事必须到位,它才能正常工作,就好像第二个片段是您的出发点和目的地一样

在接受的帖子中使用替代选项

<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"/>
更新它以定义一组顶级目的地纠正了在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中,并且我发现返回箭头似乎对导航没有影响。

对于具有类似内容的导航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>

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

按片段2上的“后退”按钮后,您想实现什么?我想退出应用程序,在这种情况下,可以。查看以获取。我甚至不知道您可以通过编程方式设置图表,谢谢。这个问题帮助了我一个小时很多!不知道你的代码为什么不工作。我正在onCreate中设置它,没有问题。你问题中的代码解决了我的问题:)thnx!clearTask标志和setClearTask从alpha02开始就被弃用了,看看这些,这对我不起作用。你把代码放在哪里?像你一样,我有一个欢迎片段,被设置为home fragment、 在welcome片段的onActivityCreated方法中,我获取我的viewmodel,检查用户是否已登录,如果已登录,我从上面尝试了您的解决方案。在第二个(新主页)片段上仍然有一个向上箭头。我如何才能
?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>
<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"/>
// Old code
val controller by lazy { findNavController(R.id.nav_host_fragment) }
val appBarConfig by lazy { AppBarConfiguration(controller.graph) }
// 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
<?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>
yourElement.setOnClickListener {
  view.findNavController().navigate(R.id.nav_users)
}