Динамический заголовок ActionBar из фрагмента с помощью AndroidX Navigation
Я использую новый компонент навигации от Android Jetpack.
Настройка корневых действий довольно проста:
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()
делает для меня. Есть ли способ динамически обновить заголовок панели действий?
16 ответов
На данный момент компоненты Jetpack Navigation Architecture не предоставляют никакого "встроенного" способа сделать это, и вам придется реализовать свой собственный "пользовательский" метод для этого.
Существует существующий запрос функции для получения функциональности для динамических меток на местах назначения, добавленных к новым компонентам архитектуры навигации Jetpack. Если вы находитесь здесь, потому что вы хотите / нуждаетесь в этой функции, отметьте существующий запрос функции здесь: https://issuetracker.google.com/issues/80267266
По состоянию на 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>
Здесь android:label
атрибут для нашего <fragment>
имеет имя аргумента в скобках ({title}
в "Title: {title}"
, Заголовок панели приложения будет затем установлен на значение метки, с {title}
заменяется значением title
аргумент.
Если вам нужно что-то более сложное, чем это - например, вы хотите посмотреть модель по ID и прочитать свойство из нее - вам нужно будет использовать больше ручных подходов, таких как описанные в других ответах на этот вопрос.
Заголовок можно изменить во фрагменте, используя:
((AppCompatActivity) requireActivity()).getSupportActionBar().setTitle("Hello");
Принимая во внимание, что ваша деятельность хоста - MainActivity, просто добавьте следующий код в функцию onCreate вашего MainActivity.
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
}
Если вы используете панель инструментов на setSupportActionBar в Activity и хотите изменить ее заголовок во фрагменте, то приведенный ниже код может вам помочь;)
(requireActivity() as MainActivity).toolbar.title = "Title here"
Удалить метку из файла graph.xml
android:label="fragment_info"
и использовать подход старой школы, если вы хотите динамически установить заголовок фрагмента из самого фрагмента
getActivity().setTitle("Your Title");
Теперь пользовательский интерфейс навигации поддерживает эту функцию. Теперь заголовок панели действий динамически меняется. Вам просто нужно настроить панель действий с помощью Nav Controller.
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()
}
Другое решение - использовать ViewModel и LiveData, прикрепить viewmodel к вашей деятельности и фрагментам, добавить поле liveata внутри viewmodel
val title = MutableLiveData<String>()
Из вашей активности наблюдайте это поле, и если оно изменилось, обновите заголовок панели инструментов
viewModel?.title?.observe(this, Observer {
my_toolbar.title=it
})
Из вашего желаемого фрагмента измените поле заголовка внутри viewmodel
viewModel?.title?.value="New title"
Пока проблема не будет исправлена, простой слушатель работает на меня:
/**
* 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")
На основе ответа @kaustubh-trivedi, и если вы используете MVVM (например, пример Android Studio FirstFragment / SecondFragment):
КОТЛИН версия
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());
}
});
Вы можете добавить addOnNavigatedListener внутри своей деятельности, и в зависимости от текущего назначения измените заголовок
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"
}
}
}
Если ваш заголовок получен из составной строки, например:
ActionBar actionBar = ((AppCompatActivity)
requireActivity()).getSupportActionBar();
if(actionBar!=null)
actionBar.setTitle(count + "items");
В соответствии с приведенными ниже решениями нет необходимости обращаться к панели инструментов вручную.
Если вы собираетесь передать Модель во фрагмент назначения. Тогда вы можете сделать как показано ниже.
Переопределить
toString()
метод в вашем классе модели
data class UserModel(
val userId: String = ""
val firstName: String = "",
val lastName: String = ""
) {
override fun toString(): String {
return "$firstName $lastName"
}
}
Теперь в
nav_grap.xml
файл, сделайте как показано ниже.
android:label="{UserData}"
будет извлекать метку из метода toString() класса модели.
<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>
Надеюсь, это будет полезно.
При попытке использовать заголовок действия, похоже, он переопределяет заголовок фрагмента. Находясь на безопасной стороне, вы должны надеть onResume
,
override fun onResume() {
super.onResume()
activity?.toolbar.title = "YOUR_TITLE_HERE"
}
меня устраивает!
Примечание: в активности должен быть виджет панели инструментов
Добавьте такую панель инструментов в 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>
Просто используйте этот код внутри yourfragment.java, если вы используете компоненты навигации и хотите программно изменить метку своего фрагмента в 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);
}
});
Вы можете удалить android:label в навигационном графике, а затем написать onCreateView()
activity?.title="your title"