使用AndroidX Navigation从Fragment动态更改ActionBar标题

88

我正在使用来自Android Jetpack的新Navigation组件。

根Activity的设置非常简单:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    setSupportActionBar(toolbar)

    val navController = findNavController(R.id.navigationFragment)
    setupActionBarWithNavController(navController)

    bottomNavigationView.setupWithNavController(navController)
}

当Fragment的标题在导航图中定义时,它能够正常工作。但是对于一个Fragment,我想动态设置标题。

我尝试了使用findNavController().currentDestination.label = "Hello world",但它没有起作用。

当然,我可以使用一些技巧,比如(activity as? AppCompatActivity)?.supportActionBar?.title = "Hello world",但我觉得这会破坏setupActionBarWithNavController()为我做的魔法。有没有办法动态更新操作栏标题?

16个回答

163

截至1.0.0-alpha08版本,您可以动态设置标题使用NavigationUI模块...如果动态模块是导航操作的参数。

因此,例如,在您的导航图中,您可以有类似以下的内容:

  <fragment
    android:id="@+id/displayFragment"
    android:name="com.commonsware.jetpack.sampler.nav.DisplayFragment"
    android:label="Title: {title}" >
    <argument
      android:name="modelId"
      app:argType="string" />
    <argument
      android:name="title"
      app:argType="string" />
  </fragment>

在这里,我们的 <fragment>android:label 属性中有一个用大括号包含的参数名称 ({title}"Title: {title}" 中)。然后应用栏的标题将被设置为标签的值,并用 {title} 替换为 title 参数的值。

如果您需要比此更复杂的内容,例如您想通过 ID 查找模型并从中读取属性,则需要使用更多手动方法,例如在本问题的其他答案中概述的方法。


2
太棒了。谢谢。 - Mehul Kanzariya
15
该解决方案在导航版本 2.1.0-alpha02 中无法正常工作。它将直接打印 Title:{title} 作为操作栏标题。 - Rohit Maurya
4
添加说明;值绑定似乎对用大括号包裹的参数中的额外空格很敏感。我注意到{ title }会导致我的应用程序崩溃,而{title}则如预期一样工作。这只是一个小问题,以防它让其他人遇到困难。 - Dacre Denny
1
@DacreDenny:不错的发现!我很容易地重现了它,并提交了一个问题 - CommonsWare
1
如果我想将一个字符串参数与一个字符串资源组合起来,这是否可能? - Benjamin Menrad
显示剩余14条评论

44

通过将活动转换为AppCompatActivity,可以在片段中更改标题。

Kotlin

(requireActivity() as AppCompatActivity).supportActionBar?.title = "Hello"

Java

((AppCompatActivity) requireActivity()).getSupportActionBar().setTitle("Hello");

到目前为止,这是唯一的解决方案,可以在导航图中定义“标签”后以编程方式更改导航UI的操作栏/应用栏。 - AdamHurwitz
简单而有效。谢谢:D - Mário Henrique
刚刚注意到Android Studio返回一个警告,说:“方法调用'getSupportActionBar'可能会产生'NullPointerException'”。我想知道除了使用(if/else)之外,是否有其他解决方法? - Ehsan Ghasaei
无法工作,因为片段引用了FragmentActivity而不是AppCompatActivity。 - JPM
@JPM不用担心。片段引用父活动。只要您的父活动是AppCompatActivity,它就可以正常工作。 - Sourav Kannantha B
一个问题是,如果你把这个放在 onViewCreated 中,在导航时它可以工作,但如果片段被旋转,它会失败,因为当调用此函数时,onViewCreated 中的操作栏不可用。相反,应该将其放在 onStart 中。 - Mike Hanafey

19

考虑到您的主机活动为MainActivity,只需将以下代码添加到MainActivityonCreate函数中即可:

val navController = Navigation.findNavController(this, R.id.nav_host_fragment)

// setting title according to fragment
navController.addOnDestinationChangedListener { 
    controller, destination, arguments ->
        toolbar.title = navController.currentDestination?.label
}

1
如果您只有一个活动,请同意此答案。只需在nav_main.xml中设置标签即可。 - Yuan Fu
标题必须来自片段,代码应该在片段本身中。活动不需要知道每个可能的片段。 这就是我们从活动实现接口以支持它们的片段转向Jetpack导航和ViewModels的原因。 - Marcus Wolschon
导航目的地已经作为参数给出,所以请使用它:toolbar.title = destination.label 或者 supportActionBar?.title = destination.label - Pnemonic

11
如果您在Activity中使用setSupportActionBar的工具栏,并且希望在片段中更改其标题,则下面的代码可能会有所帮助;)

如果您在Activity中使用setSupportActionBar的工具栏,并且希望在片段中更改其标题,则下面的代码可能会有所帮助;)

(requireActivity() as MainActivity).toolbar.title = "Title here"

9
截至目前,Jetpack导航架构组件没有提供任何“内置”的方法来实现此操作,您需要实现自己的“自定义”方法来完成它。
对于在新的Jetpack导航架构组件中添加目标动态标签功能的需求,已经存在一个功能请求。如果您来到这里是因为您想要/需要这个功能,请在这里给现有的功能请求打星: https://issuetracker.google.com/issues/80267266

我看到问题已经解决了,但是我仍然不知道正确的方法。有人知道怎么做吗? - Mehul Kanzariya
1
@MehulKanzariya:请查看我刚刚添加的这个答案 - CommonsWare

8

从 graph.xml 文件中删除标签

android:label="fragment_info"

如果你想要从片段本身动态设置标题,可以使用老派的方法。

getActivity().setTitle("Your Title");

如果您需要基于导航ID做出决策,而该ID恰好是导航图中定义的标签,则此方法无法正常工作。 - Rowan Gontier

4

另一种解决方案是使用ViewModel和LiveData。将ViewModel附加到您的活动和片段上,在ViewModel中添加一个LiveData字段。

val title = MutableLiveData<String>()

观察此字段的活动,如果发生更改则更新工具栏标题

viewModel?.title?.observe(this, Observer { 
        my_toolbar.title=it
    })

从您所需的片段中更改视图模型中的标题字段。

viewModel?.title?.value="New title"

这可能是一个解决方案。但是,使用导航组件后,在Activity中有不止一个Fragment。我该如何知道哪个Fragment在可见并如何对其进行反应?这似乎很麻烦。 - Jonas Schmid
您可以在Activity中使用多个Fragment,您可以从Fragment更改标题,无论是哪个Fragment,只需将ViewModel字段标题更改为其他内容,Activity将了解标题值的更改,并相应地更新工具栏标题。 - Alex
使用自身更改ActionBar标题的导航组件似乎并不是一个好主意。 - Jonas Schmid
2
我喜欢这个解决方案,因为当传递参数时我无法确定片段标签。所以我决定创建基本片段类,在模型中通过LiveData更改工具栏标题,并且所有子类都只需实现抽象属性来提供标题。我删除了导航图中的所有标签,因此导航组件不会产生干扰。虽然不是特别优雅,但我猜也不算太丑陋。 - Almighty

4

现在导航界面支持此功能。现在ActionBar标题会动态更改。您只需使用NavController设置ActionBar即可。

private lateinit var appBarConfiguration: AppBarConfiguration

private lateinit var navController: NavController

override fun onCreate(savedInstanceState: Bundle?) {
    preferedTheme()
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main)
    setSupportActionBar(toolbar)
    navController = findNavController(R.id.nav_controller_fragment)
    appBarConfiguration = AppBarConfiguration(navController.graph)
    setupActionBarWithNavController(navController, appBarConfiguration)
}

在导航图中设置操作栏标签:

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

<fragment android:id="@+id/mainFragment"
          android:name="com.cinderellaman.general.ui.fragments.MainFragment"
          android:label="General"
          tools:layout="@layout/main_fragment"/>

现在它还支持“向上导航”:

override fun onSupportNavigateUp(): Boolean {
    return navController.navigateUp(appBarConfiguration) || super.onSupportNavigateUp()
}

你可能想要使用navigation-ui-ktx库中提供的扩展函数来优化你的代码,例如BottomNavigationView#setupWithNavControllerAppCompatActivity#setupActionBarWithNavControllerNavController#navigateUp - JJD

2

根据 @kaustubh-trivedi 的回答,如果你正在使用 MVVM (例如 Android Studio 的 FirstFragment/SecondFragment 示例):

KOTLIN 版本

val navController = Navigation.findNavController(this, R.id.nav_host_fragment)

// setting title according to fragment
navController.addOnDestinationChangedListener { 
    controller, destination, arguments ->
        toolbar.title = navController.currentDestination?.label
}

JAVA 版本

        navController.addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
            @Override
            public void onDestinationChanged(@NonNull NavController controller, @NonNull NavDestination destination, @Nullable Bundle arguments) {
                binding.toolbar.setTitle(destination.getLabel());
            }
        });

2

在这个问题修复之前,简单的监听器对我很有效:

/**
 * Temporary solution to dynamically change title of actionbar controlled by Navigation component
 * Should be removed as soon as the bug on Navigation will be fixed: (https://issuetracker.google.com/issues/80267266)
 */
interface TempToolbarTitleListener {
    fun updateTitle(title: String)
}

class MainActivity : AppCompatActivity(), TempToolbarTitleListener {

    ...

    override fun updateTitle(title: String) {
        binding.toolbar.title = title
    }
}

从片段中更改标题:

(activity as TempToolbarTitleListener).updateTitle("custom title")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接