Как установить заголовок на панели приложения с помощью компонента архитектуры навигации

Я пробовал компонент архитектуры навигации и теперь испытываю трудности с установкой заголовка. Как программно установить заголовок и как он работает?

Чтобы прояснить мой вопрос, давайте приведем пример, где я установил простое приложение с MainActivity, на котором размещен хост-контроллер навигации, у MainFragment есть кнопка, и при нажатии кнопки он переходит на DetailFragment.

Тот же код из другого вопроса несколько панелей приложений о переполнении стека.

Основная деятельность

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Toolbar toolbar = findViewById(R.id.toolbar);
        setSupportActionBar(toolbar);

        // Setting up a back button
        NavController navController = Navigation.findNavController(this, R.id.nav_host);
        NavigationUI.setupActionBarWithNavController(this, navController);
    }

    @Override
    public boolean onSupportNavigateUp() {
        return Navigation.findNavController(this, R.id.nav_host).navigateUp();
    }
}

MainFragment

public class MainFragment extends Fragment {

    public MainFragment() {
        // Required empty public constructor
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_main, container, false);
    }

    @Override
    public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
        super.onViewCreated(view, savedInstanceState);
        Button buttonOne = view.findViewById(R.id.button_one);
        buttonOne.setOnClickListener(Navigation.createNavigateOnClickListener(R.id.detailFragment));
    }

}

ДетальФрагмент

public class DetailFragment extends Fragment {

    public DetailFragment() {
        // Required empty public constructor
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_detail, container, false);
    }
}

activity_main.xml

<?xml version = "1.0" encoding = "utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout 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:layout_width = "match_parent"
    android:layout_height = "match_parent"
    android:animateLayoutChanges = "true"
    tools:context = ".MainActivity">

    <com.google.android.material.appbar.AppBarLayout
        android:layout_width = "match_parent"
        android:layout_height = "wrap_content"
        android:animateLayoutChanges = "true"
        android:theme = "@style/AppTheme.AppBarOverlay">

        <androidx.appcompat.widget.Toolbar
            android:id = "@+id/toolbar"
            android:layout_width = "match_parent"
            android:layout_height = "?attr/actionBarSize"
            android:background = "?attr/colorPrimary"
            app:popupTheme = "@style/AppTheme.PopupOverlay" />

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

    <fragment
        android:id = "@+id/nav_host"
        android:name = "androidx.navigation.fragment.NavHostFragment"
        android:layout_width = "match_parent"
        android:layout_height = "match_parent"
        android:layout_gravity = "top"
        android:layout_marginTop = "?android:attr/actionBarSize"
        app:defaultNavHost = "true"
        app:layout_anchor = "@id/bottom_appbar"
        app:layout_anchorGravity = "top"
        app:layout_behavior = "@string/appbar_scrolling_view_behavior"
        app:navGraph = "@navigation/mobile_navigation" />

    <com.google.android.material.bottomappbar.BottomAppBar
        android:id = "@+id/bottom_appbar"
        android:layout_width = "match_parent"
        android:layout_height = "?android:attr/actionBarSize"
        android:layout_gravity = "bottom" />

    <com.google.android.material.floatingactionbutton.FloatingActionButton
        android:id = "@+id/fab"
        android:layout_width = "wrap_content"
        android:layout_height = "wrap_content"
        app:layout_anchor = "@id/bottom_appbar" />

</androidx.coordinatorlayout.widget.CoordinatorLayout>

navigation.xml

<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.example.MainFragment"
        android:label = "fragment_main"
        tools:layout = "@layout/fragment_main" >
        <action
            android:id = "@+id/toAccountFragment"
            app:destination = "@id/detailFragment" />
    </fragment>
    <fragment
        android:id = "@+id/detailFragment"
        android:name = "com.example.DetailFragment"
        android:label = "fragment_account"
        tools:layout = "@layout/fragment_detail" />
</navigation>

Поэтому при запуске моего приложения название - «MainActivity». Как обычно, он показывает MainFragment, содержащий кнопку для перехода к DetailFragment. В DialogFragment я установил заголовок как:

getActivity().getSupportActionBar().setTitle("Detail");

Первая проблема: Итак, нажав кнопку на MainFragment, чтобы перейти к DetailFragment, он переместится туда, и название изменится на «Подробности». Но при нажатии кнопки возврата название меняется на "fragment_main". Итак, я добавил эту строку кода в MainFragment:

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    // ...

    //Showing the title 
    Navigation.findNavController(view)
      .getCurrentDestination().setLabel("Hello");
}

Теперь, когда мы возвращаемся с DetailFragment на MainFragment, название меняется на «Привет». Но вот вторая проблема, когда я закрываю приложение и запускаю снова, заголовок снова меняется на «MainActivity», хотя вместо этого должно отображаться «Hello», понимаете?

Хорошо, тогда добавление setTitle("Hello") в MainFrgment тоже не работает. Например, действие начинается с заголовком «Привет», перейдите на DetailsFragment и снова нажмите кнопку «Назад», заголовок вернется к «fragment_main».

Единственное решение - иметь как setTitle("Hello"), так и Navigation.findNavController(view).getCurrentDestination().setLabel("Hello") в MainFragment.

Итак, как правильно отображать заголовки фрагментов с помощью компонента навигации?

возможно дублирован из stackoverflow.com/questions/50599238/…

Agna JirKon Rx 26.07.2019 00:46

Отвечает ли это на ваш вопрос? Динамический заголовок ActionBar из фрагмента с помощью AndroidX Navigation

Adam Hurwitz 14.12.2020 19:15
48
2
36 018
17
Перейти к ответу Данный вопрос помечен как решенный

Ответы 17

На самом деле это из-за:

android:label = "fragment_main"

Который вы установили в файле xml.

So what is the proper way to show the title for Fragments using Navigation Component?

setTitle() работает на этом этапе. Но поскольку вы установили метку для этих Fragment, она может снова отобразить метку при воссоздании Activity. Решением, вероятно, будет удаление android:label, а затем выполнение ваших действий с помощью кода:

((AppCompatActivity) getActivity()).getSupportActionBar().setTitle("your title");

Или:

((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle("your subtitle");

В onCreateView().


Нашел обходной путь:

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

Проверьте это тоже: Динамический заголовок ActionBar из фрагмента с помощью AndroidX Navigation

Я уже пробовал это, и в результате, если я не использую Navigation.findNavController(view).getCurrentDestination().s‌​etLabel("Hello") в MainFragment, заголовок останется «Подробно». И не знаю почему, кто-то проголосовал против: p.

Rajarshi 26.09.2018 12:20

Я хочу внести ясность: добавление setTitle вместе с Navigation.findNavController(view).getCurrentDestination().s‌​etLabel("Hello") кажется единственным способом получить название, даже если я запускаю приложение или нажимаю кнопку «Назад». Так разве нет жизнеспособного способа с единственной строчкой кода? Хорошо, позвольте мне обновить вопрос.

Rajarshi 26.09.2018 12:32

Итак, проблема с компонентом навигации.

Rajarshi 26.09.2018 13:10

Редактирование строки заголовка вручную или при условии, что содержимое getCurrentDestination синхронизируется с NavController. Правильное решение находится на stackoverflow.com/questions/50599238/…

gladed 31.12.2019 02:54

Я бы посоветовал включить AppBar на каждый экран. Чтобы избежать дублирования кода, создайте помощник, который строит AppBar, принимая заголовок в качестве параметра. Затем вызовите помощник в каждом классе экрана.

Ответ принят как подходящий

Поскольку другие все еще участвуют в ответе на этот вопрос, позвольте мне ответить мой собственный вопрос, поскольку с тех пор API изменились.

Сначала удалите android:label из фрагмента / фрагментов, название которых вы хотите изменить, из navigation.xml (он же навигационный график).

Теперь вы можете изменить название с Fragment, позвонив

(requireActivity() as MainActivity).title = "My title"

Но лучше всего использовать API NavController.addOnDestinationChangedListener из MainActivity. Пример:

NavController.OnDestinationChangedListener { controller, destination, arguments ->
// compare destination id
    title = when (destination.id) {
        R.id.someFragment -> "My title"
        else -> "Default title"
    }

//    if (destination == R.id.someFragment) {
//        title = "My title"
//    } else {
//        title = "Default Title"        
//    }
}

Если вам вообще не нужен заголовок, вы не можете указать null или пустую строку. Вместо этого вы должны установить destination.label на " ". Я добавил это как пример в ваш код (должно быть видно после экспертной оценки моих изменений)

muetzenflo 16.08.2019 17:00

@muetzenflo: это было исправлено, начиная с интерфейса навигации "2.3.0-alpha04"

Ally 02.05.2020 14:42

Или вы можете установить непосредственно destination.label, и ярлык appBar изменится. OnDestinationChangedListener необходимо настроить перед вызовом NavigationUI.setupActionBarWithNavController(...).

Vít Kapitola 22.07.2020 11:47
(requireActivity() as MainActivity).title не меняет метку даже после удаления метки из навигационного графика. Возможно, функционал API недавно изменился.
Adam Hurwitz 13.12.2020 03:29

Или просто добавьте метки ко всем фрагментам на графике навигационного приложения и установите title = destination.label, как в: findNavController(R.id.nav_host_fragment).addOnDestinationCh‌​angedListener { controller, destination, arguments -> title = destination.label }

MiStr 13.02.2021 18:04

По опыту, NavController.addOnDestinationChangedListener

Кажется, работает хорошо. Мой пример ниже на моей MainActivity сотворил чудо

navController.addOnDestinationChangedListener{ controller, destination, arguments ->
        title = when (destination.id) {
           R.id.navigation_home -> "My title"
            R.id.navigation_task_start -> "My title2"
            R.id.navigation_task_finish -> "My title3"
            R.id.navigation_status -> "My title3"
            R.id.navigation_settings -> "My title4"
            else -> "Default title"
        }

    }

для всех, кто такой же глуп, как я, и задается вопросом, почему это не работает: вам нужно добавить supportActionBar?.title = title внизу, чтобы применить заголовок к панели действий

Max90 03.04.2020 07:27

Вы также можете установить заголовок с помощью destination.setLabel(), но он должен быть перед вызовом `NavigationUI.setupActionBarWithNavController (...); метод. `

Vít Kapitola 22.07.2020 10:53

В настоящее время есть гораздо более простой способ добиться этого с помощью Kotlin и androidx.navigation:navigation-ui-ktx:

import androidx.navigation.ui.setupActionBarWithNavController

class MainActivity : AppCompatActivity() {

    private val navController: NavController
        get() = findNavController(R.id.nav_host_fragment)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val binding = DataBindingUtil.setContentView<ActivityMainBinding>(this,R.layout.activity_main)
        setSupportActionBar(binding.toolbar)
        setupActionBarWithNavController(navController) // <- the most important line
    }

    // Required by the docs for setupActionBarWithNavController(...)
    override fun onSupportNavigateUp() = navController.navigateUp()
}

Вот в основном это. Не забудьте указать android:label в ваших навигационных графиках.

Другой подход может быть таким:

fun Fragment.setToolbarTitle(title: String) {
    (activity as NavigationDrawerActivity).supportActionBar?.title = title
}

API мог измениться с тех пор, как был задан вопрос, но теперь вы можете указать ссылку на строку в ресурсах вашего приложения в метке графа навигации.

Это очень полезно, если вам нужен статический заголовок для ваших фрагментов.

что, если вы хотите повторно использовать этот фрагмент и вам нужно изменить метку? Как я буду делать это программно

Shikhar 07.05.2020 12:40

Вы можете использовать этот код в своем фрагменте, если вы не укажете панель своего приложения (панель приложений по умолчанию)

(activity as MainActivity).supportActionBar?.title = "Your Custom Title"

Не забудьте удалить атрибут android:label в навигационном графике.

Счастливый код ^ - ^

Вы можете использовать xml-файл с навигационным графом и установить метку фрагмента в качестве аргумента. Затем в родительском фрагменте вы можете передать аргумент с помощью SafeArgs (пожалуйста, следуйте руководству по https://developer.android.com/guide/navigation/navigation-pass-data#Safe-args для настройки SafeArgs) и укажите значение по умолчанию, чтобы заголовок не был пустым или пустым.

<!--this is originating fragment-->
<fragment
        android:id = "@+id/fragmentA"
        android:name = ".ui.FragmentA"
        tools:layout = "@layout/fragment_a">
        <action
            android:id = "@+id/fragmentBAction"
            app:destination = "@id/fragmentB" />
</fragment>

<!--set the fragment's title to a string passed as an argument-->
<!--this is a destination fragment (assuming you're navigating FragmentA to FragmentB)-->
<fragment
    android:id = "@+id/fragmentB"
    android:name = "ui.FragmentB"
    android:label = "{myTitle}"
    tools:layout = "@layout/fragment_b">
    <argument
        android:name = "myTitle"
        android:defaultValue = "defaultTitle"
        app:argType = "string" />
</fragment>

В исходном фрагменте:

public void onClick(View view) {
     String text = "text to pass as a title";
     FragmentADirections.FragmentBAction action = FragmentADirections.fragmentBAction();
     action.setMyTitle(text);
     Navigation.findNavController(view).navigate(action);
}

FragmentADirections и FragmentBAction - Эти классы автоматически создаются из идентификаторов в вашем файле nav_graph.xml. В классах типа 'ваш идентификатор действия + действие' вы можете найти автоматически сгенерированные методы установки, которые вы можете использовать для передачи аргументов.

В пункте назначения вы вызываете автоматически сгенерированный класс {идентификатор вашего фрагмента получения} + Args

FragmentBArgs.fromBundle(requireArguments()).getMyTitle();

Пожалуйста, обратитесь к официальному руководству по адресу https://developer.android.com/guide/navigation/navigation-pass-data#Safe-args

Не могли бы вы дополнить свое предложение командами, необходимыми для предоставления аргумента XML-файлу с помощью SafeArgs?

Aliton Oliveira 22.04.2020 18:10
NavigationUI.setupActionBarWithNavController(this, navController)

Don't forget to specify android:label for your fragments in your nav graphs.

Чтобы вернуться назад:

override fun onSupportNavigateUp(): Boolean {
    return NavigationUI.navigateUp(navController, null)
}

удалить метку детального фрагмента в xml-файле навигационного графика. затем передайте предпочтительный заголовок через аргументы целевому фрагменту, которому нужен такой заголовок. Код первого фрагмента - начальная точка

findNavController().navigate(
            R.id.action_FirstFragment_to_descriptionFragment,
            bundleOf(Pair("toolbar_title", "My Details Fragment Title"))
        )

как вы видите, я отправил как пару в пакете аргументов при переходе к целевому фрагменту
поэтому в вашем целевом фрагменте получите заголовок из аргументов в методе onCreate, например this

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        toolbarTitle = requireArguments().getString("toolbar_title", "")
    }

then use it to change Main Activity's title in onCreateView method like this
requireActivity().toolbar.title = toolbarTitle

Обновить заголовок с помощью метки в xml навигации или исключить метки и установить с помощью requiresActivity().title. Поддерживает смешивание двух способов для экранов с динамическими заголовками и без них. У меня работает с панелью инструментов Compose UI и вкладками.

    val titleLiveData = MutableLiveData<String>()
    findNavController().addOnDestinationChangedListener { _, destination, _ ->
        destination.label?.let {
            titleLiveData.value = destination.label.toString()
        }
    }

    (requireActivity() as AppCompatActivity).setSupportActionBar(object: Toolbar(requireContext()) {
        override fun setTitle(title: CharSequence?) {
            titleLiveData.value = title.toString()
        }
    })

    titleLiveData.observe(viewLifecycleOwner, { 
        // Update your title
    })

В любом случае некоторые из тех ответов, которые я пробовал, не сработали, поэтому я решил сделать это старым способом Java в Kotlin, используя интерфейс

Создан интерфейс, как показано ниже.

interface OnTitleChangeListener {
   fun onTitleChange(app_title: String)
}

Затем я предпринял действия по реализации этого интерфейса, как показано ниже.

class HomeActivity : AppCompatActivity(), OnTitleChangeListener {
    override fun onTitleChange(app_title: String) {
    title = app_title
    }
}

Как на моем фрагменте по активности атташе я это

override fun onAttach(context: Context) {
    super.onAttach(context)
    this.currentContext = context
    (context as HomeActivity).onTitleChange("New Title For The app")
}

Я потратил пару часов, пытаясь сделать очень простую вещь, например, изменить заголовок панели детального фрагмента при переходе от отдельного элемента к главному фрагменту. Мой папа всегда говорил мне: K.I.S.S. Будь простым сыном. setTitle () потерпел крах, интерфейсы громоздкие, придумали (очень) простое решение с точки зрения строк кода для последней реализации навигации по фрагментам. В главном фрагменте разрешите NavController, получите NavGraph, найдите целевой узел, установите заголовок и, наконец, что не менее важно, перейдите к нему:

//----------------------------------------------------------------------------------------------

private void navigateToDetail () {

NavController navController = NavHostFragment.findNavController(FragmentMaster.this);
navController.getGraph().findNode(R.id.FragmentDetail).setLabel("Master record detail");
navController.navigate(R.id.action_FragmentMaster_to_FragmentDetail,null);

}

Простое решение:

Макет

androidx.coordinatorlayout.widget.CoordinatorLayout
  com.google.android.material.appbar.AppBarLayout
    com.google.android.material.appbar.MaterialToolbar
  
  androidx.constraintlayout.widget.ConstraintLayout
    com.google.android.material.bottomnavigation.BottomNavigationView
    fragment
      androidx.navigation.fragment.NavHostFragment 
    

Деятельность

    binding = DataBindingUtil.setContentView(this, layoutRes)
    setSupportActionBar(binding.toolbar)
    
    val controller = findNavController(R.id.nav_host)
    val configuration = AppBarConfiguration(
        setOf(
            R.id.navigation_home,
            R.id.navigation_dashboard,
            R.id.navigation_notifications
        )
    )
    setupActionBarWithNavController(controller, configuration)
    navView.setupWithNavController(controller)

Это может быть полезно, если вы хотите программно изменить название панели инструментов с помощью кода с низкой связностью между Activity и Fragment.


class DetailsFragment : Fragment() {

    interface Callbacks {
        fun updateTitle(title: String)
    }

    private var listener: Callbacks? = null

    override fun onAttach(context: Context) {
        super.onAttach(context)
        // keep activity as interface only
        if (context is Callbacks) {
            listener = context
        }
    }

    override fun onDetach() {
        // forget about activity
        listener = null
        super.onDetach()
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View =
        inflater.inflate(R.layout.fragment_details, container, false)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        listener?.updateTitle("Dynamic generated title")
    }

}

class MainActivity : AppCompatActivity(), DetailsFragment.Callbacks {

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

    override fun updateTitle(title: String) {
        supportActionBar?.title = title
    }

}

4 сентября 2021 г.

Использование Котлина в моем случае это решение. используйте этот код в MainActivity

 val navController = this.findNavController(R.id.myNavHostFragment)
     navController.addOnDestinationChangedListener { controller, destination, arguments ->
                destination.label = when (destination.id) {
                    R.id.homeFragment -> resources.getString(R.string.app_name)
                    R.id.roomFloorTilesFragment -> resources.getString(R.string.room_floor_tiles)
                    R.id.roomWallTilesFragment -> resources.getString(R.string.room_wall_tiles)
                    R.id.ceilingRateFragment -> resources.getString(R.string.ceiling_rate)
                    R.id.areaConverterFragment -> resources.getString(R.string.area_converter)
                    R.id.unitConverterFragment -> resources.getString(R.string.unit_converter)
                    R.id.lenterFragment -> resources.getString(R.string.lenter_rate)
                    R.id.plotSizeFragment -> resources.getString(R.string.plot_size)
                    else -> resources.getString(R.string.app_name)
                }
            }
            NavigationUI.setupActionBarWithNavController(...)
            NavigationUI.setupWithNavController(...)

Другие вопросы по теме