Динамический заголовок 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"
Другие вопросы по тегам