如何以编程方式更改导航图的起始目的地?
How to change start destination of a navigation graph programmatically?
基本上,我有以下导航图:
我想在到达后立即将导航图中的起点更改为 fragment 2
(以防止在按下后退按钮时返回到 fragment 1
- 就像启动画面一样)。
这是我的代码:
navGraph = navController.getGraph();
navGraph.setStartDestination(R.id.fragment2);
navController.setGraph(navGraph);
但是,显然它不起作用,在按下后退按钮后它返回 fragment 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"/>
并且您想导航到第二个片段并使其成为图形的根,请指定下一个 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)
- Pop up to a given destination before navigating. This pops all non-matching destinations from the back stack until this destination is found.
destinationId
- The destination to pop up to, clearing all intervening destinations.
inclusive
- true to also pop the given destination from the back stack.
备选方案:
<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
属性和 NavOptions
中的关联 API 已被弃用。
来源:https://developer.android.com/jetpack/docs/release-notes
如果您想将根片段更改为 fragment 2
(例如,在 fragment 2
上按返回按钮后您将退出应用程序),您应该将下一个属性添加到 action
或 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 1
到 fragment 2
的导航时,请使用下一个代码:
Navigation.findNavController(view)
.navigate(R.id.action_firstFragment_to_secondFragment);
我找到了解决这个问题的方法,但它很难看。我想这在 alpha 库中是意料之中的,但我希望 Google 研究 simplifying/fixing 因为这是一种非常流行的导航模式。
Alexey 的解决方案对我不起作用。我的问题是我的 Actionbar 上显示了向上箭头,方法是:
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
}
然后在片段 #1 中Activity创建,根据 Alexey 的建议:
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()
只是在 activity 中运行一个方法来更改图形的 startDestination。我可以清理它并将其变成一个界面,但我会等待 Google 并希望他们改进整个过程。方法 'setPopUpTo' 对我来说似乎命名不当,而且您命名从图中切出的片段并不直观。他们在 navOptions 中进行这些更改对我来说也很奇怪。我认为 navOptions 只会与它们所连接的导航操作有关。
我什至不知道 navHost.navController.graph = graph
是做什么的,但没有它,向上箭头 return。 :(
我正在使用 Navigation 1.0.0-alpha06。
您真的不需要弹出 Splash Fragment。它可以在您的 App 余生中一直保留在那里。您应该做的是从初始屏幕确定要显示的下一个屏幕。
上图可以在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)
您也可以尝试以下方法。
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>
好吧,在稍微弄乱了这个之后,我找到了一个不需要大量工作的适合我的解决方案。
似乎必须有两件事才能发挥作用,就像您的 secondFragment 是您的起始目的地一样。
在已接受的 post
中使用 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) }
更新它以定义一组 top-level 个目的地纠正了在 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>
假设当前打开的片段是主页片段,并且您想导航到用户片段,为此只需调用您想要从导航控制器导航到导航方法的元素的 setOnClickListener,类似于此代码:
yourElement.setOnClickListener {
view.findNavController().navigate(R.id.nav_users)
}
这将使应用导航到另一个片段,并且还将处理工具栏中的标题。
我尝试通过代码修改startDestination
。
它看起来运行良好,但在不保留 activity 的情况下,导航组件不会恢复片段堆栈。
我用虚拟 startDestination
解决了这个问题
- startDestination 是 EmptyFragment(只是一个虚拟对象)
- EmptyFragment 到 FirstFragment 操作需要
popUpTo=EmptyFragment
和 popUpToInclusive=true
NavGraph image
在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不为空,片段自动恢复。
基本上,我有以下导航图:
我想在到达后立即将导航图中的起点更改为 fragment 2
(以防止在按下后退按钮时返回到 fragment 1
- 就像启动画面一样)。
这是我的代码:
navGraph = navController.getGraph();
navGraph.setStartDestination(R.id.fragment2);
navController.setGraph(navGraph);
但是,显然它不起作用,在按下后退按钮后它返回 fragment 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"/>
并且您想导航到第二个片段并使其成为图形的根,请指定下一个 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)
- Pop up to a given destination before navigating. This pops all non-matching destinations from the back stack until this destination is found.
destinationId
- The destination to pop up to, clearing all intervening destinations.
inclusive
- true to also pop the given destination from the back stack.
备选方案:
<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
属性和 NavOptions
中的关联 API 已被弃用。
来源:https://developer.android.com/jetpack/docs/release-notes
如果您想将根片段更改为 fragment 2
(例如,在 fragment 2
上按返回按钮后您将退出应用程序),您应该将下一个属性添加到 action
或 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 1
到 fragment 2
的导航时,请使用下一个代码:
Navigation.findNavController(view)
.navigate(R.id.action_firstFragment_to_secondFragment);
我找到了解决这个问题的方法,但它很难看。我想这在 alpha 库中是意料之中的,但我希望 Google 研究 simplifying/fixing 因为这是一种非常流行的导航模式。
Alexey 的解决方案对我不起作用。我的问题是我的 Actionbar 上显示了向上箭头,方法是:
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
}
然后在片段 #1 中Activity创建,根据 Alexey 的建议:
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()
只是在 activity 中运行一个方法来更改图形的 startDestination。我可以清理它并将其变成一个界面,但我会等待 Google 并希望他们改进整个过程。方法 'setPopUpTo' 对我来说似乎命名不当,而且您命名从图中切出的片段并不直观。他们在 navOptions 中进行这些更改对我来说也很奇怪。我认为 navOptions 只会与它们所连接的导航操作有关。
我什至不知道 navHost.navController.graph = graph
是做什么的,但没有它,向上箭头 return。 :(
我正在使用 Navigation 1.0.0-alpha06。
您真的不需要弹出 Splash Fragment。它可以在您的 App 余生中一直保留在那里。您应该做的是从初始屏幕确定要显示的下一个屏幕。
上图可以在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)
您也可以尝试以下方法。
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>
好吧,在稍微弄乱了这个之后,我找到了一个不需要大量工作的适合我的解决方案。
似乎必须有两件事才能发挥作用,就像您的 secondFragment 是您的起始目的地一样。
在已接受的 post
中使用 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) }
更新它以定义一组 top-level 个目的地纠正了在 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>
假设当前打开的片段是主页片段,并且您想导航到用户片段,为此只需调用您想要从导航控制器导航到导航方法的元素的 setOnClickListener,类似于此代码:
yourElement.setOnClickListener {
view.findNavController().navigate(R.id.nav_users)
}
这将使应用导航到另一个片段,并且还将处理工具栏中的标题。
我尝试通过代码修改startDestination
。
它看起来运行良好,但在不保留 activity 的情况下,导航组件不会恢复片段堆栈。
我用虚拟 startDestination
- startDestination 是 EmptyFragment(只是一个虚拟对象)
- EmptyFragment 到 FirstFragment 操作需要
popUpTo=EmptyFragment
和popUpToInclusive=true
NavGraph image
在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不为空,片段自动恢复。