使用 AndroidX 导航从片段中获取动态 ActionBar 标题

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

根 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)
}

当片段的标题在导航图中定义时,它工作得很好。但是对于一个片段,我想动态地设置标题。

我试过用 findNavController().currentDestination.label = "Hello world"但是没用。

我当然可以使用像 (activity as? AppCompatActivity)?.supportActionBar?.title = "Hello world"这样的技巧,但是我觉得它会打破 setupActionBarWithNavController()为我带来的魔力。有什么方法可以动态更新动作栏的标题吗?

40627 次浏览

You can add addOnNavigatedListener inside your activity, and based on current destination change the title

 findNavController(nav_host_fragment).addOnNavigatedListener { controller, destination ->
when(destination.id) {
R.id.destination1 -> {
my_toolbar.title= "Some title"
}
R.id.destination2 -> {
my_toolbar.title= "Othertitle"


}


}
}

Another solution is to use ViewModel and LiveData, attach viewmodel to your activity and fragments, add a livedata field inside viewmodel

val title = MutableLiveData<String>()

From your activity observe this field, and if it is changed update the toolbar title

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

From your desired fragment change the title field inside the viewmodel

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

As of now, The Jetpack Navigation Architecture components do not provide any "built in" way to do this, and you'll have to implement your own "custom" method for doing it.

There is an existing feature request to get functionality for dynamic labels on destinations added to the new Jetpack Navigation Architecture Components. If you are here because you want/need this functionality, please star the existing feature request, here: https://issuetracker.google.com/issues/80267266

Until the issue will be fixed, simple listener is working to me:

/**
* 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
}
}

change title from fragment:

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

Taking consideration that your host activity is MainActivity, just add the following code to your MainActivity's onCreate fun

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
}

Remove label from graph.xml file

android:label="fragment_info"

and use old school approach if you want to set title of the fragment dynamically from the fragment itself

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

On trying the activity's title it seems to override the title for fragment. Being on safe side you must put on onResume.

override fun onResume() {
super.onResume()
activity?.toolbar.title = "YOUR_TITLE_HERE"
}

it works for me !

Note : Must have Toolbar Widget in activity

Add toolbar like this in your activity's xml

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"">


<com.google.android.material.appbar.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">


<androidx.appcompat.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content" />


</com.google.android.material.appbar.AppBarLayout>


<!-- Other Widgets -->


</androidx.coordinatorlayout.widget.CoordinatorLayout>

As of 1.0.0-alpha08, you can have the NavigationUI bits dynamically set the title... if the dynamic bits are arguments on the navigation action.

So, for example, in your navigation graph, you could have something like this:

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

Here, the android:label attribute for our <fragment> has an argument name wrapped in braces ({title} in "Title: {title}". The app bar's title will then be set to the value of the label, with {title} replaced by the value of the title argument.

If you need something more elaborate than that — for example, you want to look up the model by ID and read a property from it — you will need to use more manual approaches, such as those outlined in other answers to this question.

Well, now the Navigation UI supports this feature. Now the ActionBar title changes dynamically. You just have to setup the ActionBar with the NavController.

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)
}

And set action bar label in nav graph:

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

And now its also support Navigate Up:

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

you can remove android:label in navigation graph then write in onCreateView()

activity?.title="your title"

If you use toolbar on setSupportActionBar in Activity and you would like to change its title in fragment, then below code may gonna help you ;)

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

The title can be changed in the fragment by casting the activity as AppCompatActivity.

Kotlin

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

Java

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

If your title is received from a concatenated string, for example:

ActionBar actionBar = ((AppCompatActivity)
requireActivity()).getSupportActionBar();
  

if(actionBar!=null)
actionBar.setTitle(count + "items");

As per the below solutions, there is no need to access the toolbar manually.

If you are going to pass the Model into the destinations fragment. Then you can do as below.

Override toString() method in your model class

data class UserModel(
val userId: String = ""
val firstName: String = "",
val lastName: String = ""
) {


override fun toString(): String {
return "$firstName $lastName"
}
}

Now, in the nav_grap.xml file, do as below. android:label="{UserData}" will be fetch label from toString() method of model class.

<fragment
android:id="@+id/messagesFragment"
android:name="com.app.mydemoapp.ui.messages.MessagesFragment"
android:label="{UserData}"
tools:layout="@layout/fragment_messages">


<argument
android:name="UserData"
app:argType="com.app.mydemoapp.model.UserModel" />


</fragment>

I hope, this will be helpful.

Based on @kaustubh-trivedi answer, and if you are using MVVM (like the Android Studio FirstFragment/SecondFragment example):

KOTLIN version

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 version

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

Just use this code inside yourfragment.java if you are using navigation components , and you want to change the label of your fragment programmatically in JAVA

  Navigation.findNavController(view).addOnDestinationChangedListener(new NavController.OnDestinationChangedListener() {
@Override
public void onDestinationChanged(@NonNull NavController navController, @NonNull NavDestination navDestination, @Nullable Bundle bundle) {


navController.findDestination(R.id.YOUR_FRAGMENT_ID_HERE).setLabel(YOUR_LABEL_HERE);


}
});