Я пробовал компонент архитектуры навигации и теперь испытываю трудности с установкой заголовка. Как программно установить заголовок и как он работает?
Чтобы прояснить мой вопрос, давайте приведем пример, где я установил простое приложение с 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.
Итак, как правильно отображать заголовки фрагментов с помощью компонента навигации?
Отвечает ли это на ваш вопрос? Динамический заголовок ActionBar из фрагмента с помощью AndroidX Navigation
На самом деле это из-за:
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().setLabel("Hello") в MainFragment, заголовок останется «Подробно». И не знаю почему, кто-то проголосовал против: p.
Я хочу внести ясность: добавление setTitle вместе с Navigation.findNavController(view).getCurrentDestination().setLabel("Hello") кажется единственным способом получить название, даже если я запускаю приложение или нажимаю кнопку «Назад». Так разве нет жизнеспособного способа с единственной строчкой кода? Хорошо, позвольте мне обновить вопрос.
Итак, проблема с компонентом навигации.
Редактирование строки заголовка вручную или при условии, что содержимое getCurrentDestination синхронизируется с NavController. Правильное решение находится на stackoverflow.com/questions/50599238/…
Я бы посоветовал включить 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: это было исправлено, начиная с интерфейса навигации "2.3.0-alpha04"
Или вы можете установить непосредственно destination.label, и ярлык appBar изменится. OnDestinationChangedListener необходимо настроить перед вызовом NavigationUI.setupActionBarWithNavController(...).
(requireActivity() as MainActivity).title не меняет метку даже после удаления метки из навигационного графика. Возможно, функционал API недавно изменился.
Или просто добавьте метки ко всем фрагментам на графике навигационного приложения и установите title = destination.label, как в: findNavController(R.id.nav_host_fragment).addOnDestinationChangedListener { controller, destination, arguments -> title = destination.label }
По опыту, 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 внизу, чтобы применить заголовок к панели действий
Вы также можете установить заголовок с помощью destination.setLabel(), но он должен быть перед вызовом `NavigationUI.setupActionBarWithNavController (...); метод. `
В настоящее время есть гораздо более простой способ добиться этого с помощью 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 мог измениться с тех пор, как был задан вопрос, но теперь вы можете указать ссылку на строку в ресурсах вашего приложения в метке графа навигации.
Это очень полезно, если вам нужен статический заголовок для ваших фрагментов.
что, если вы хотите повторно использовать этот фрагмент и вам нужно изменить метку? Как я буду делать это программно
Вы можете использовать этот код в своем фрагменте, если вы не укажете панель своего приложения (панель приложений по умолчанию)
(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?
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", "")
}
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
}
}
Использование Котлина в моем случае это решение. используйте этот код в 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(...)
возможно дублирован из stackoverflow.com/questions/50599238/…