我正在尝试使用导航架构组件,现在在设置标题时遇到了困难。如何以编程方式设置标题以及其工作方式?
为解决我的问题,让我们举个例子,在这里,我建立了一个简单的应用,其中MainActivity
托管了导航主机控制器,MainFragment
有一个按钮,然后单击该按钮转到DetailFragment
。
另一个问题multiple app bars的堆栈溢出中的相同代码。
MainActivity
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));
}
}
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
的同时标题变为“ Hello”。但是出现了第二个问题,当我关闭应用程序并重新启动时,标题会变回“ MainActivity”,尽管它应该显示“ Hello”,知道吗?
好的,然后在setTitle("Hello")
中添加MainFrgment
也不起作用。例如,活动开始,标题为“ Hello”,转到DetailsFragment
,然后再次按返回按钮,标题回到“ fragment_main”。
唯一的解决方案是在setTitle("Hello")
中同时包含Navigation.findNavController(view).getCurrentDestination().setLabel("Hello")
和MainFragment
。
那么使用导航组件显示片段标题的正确方法是什么?
答案 0 :(得分:10)
如果您未指定应用栏(默认应用栏),则可以在片段中使用此代码
(activity as MainActivity).supportActionBar?.title = "Your Custom Title"
请记住要删除导航图中的android:label
属性
快乐代码^-^
答案 1 :(得分:6)
实际上是因为:
android:label="fragment_main"
您已在xml中设置。
那么使用
Fragment
显示标题的正确方法是 导航组件?
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")
也请检查一下:Dynamic ActionBar title from a Fragment using AndroidX Navigation
答案 2 :(得分:3)
根据经验,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"
}
}
答案 3 :(得分:3)
您可以使用导航图xml文件,并将片段的标签设置为参数。 然后,在父片段中,可以使用SafeArgs传递参数,并提供默认值以避免标题为null或为空。
<!--set the fragment's title to characters name passed as an argument-->
<fragment
android:id="@+id/characterDetailFragment2"
android:name="ui.character.CharacterDetailFragment"
android:label="{character_name}"
tools:layout="@layout/fragment_character_detail">
<argument
android:name="character_name"
android:defaultValue="Name"
app:argType="string" />
</fragment>
在原始片段中:
String text = "text to pass";
CharactersListFragmentDirections.CharacterDetailAction action = CharactersListFragmentDirections.characterDetailAction();
action.setCharacterName(text);
Navigation.findNavController(v).navigate(action);
CharactersListFragmentDirections和CharacterDetailAction- 这些类是根据nav_graph.xml文件中的ID自动生成的。 在“您的动作ID +动作”类型类中,您可以找到自动生成的setter方法,可用于传递参数
<fragment
android:id="@+id/charactersListFragment"
android:name=".character.CharactersListFragment"
android:label="List of Characters"
tools:layout="@layout/fragment_characters_list">
<action
android:id="@+id/characterDetailAction"
app:destination="@id/characterDetailFragment2"
app:enterAnim="@anim/slide_in_right"
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
</fragment>
在接收目的地(xml布局文件位于此答案的顶部)中,您调用自动生成的类{您的接收片段ID} + Args
CharacterDetailFragmentArgs.fromBundle(requireArguments()).getCharacterName();
请参考官方指南 https://developer.android.com/guide/navigation/navigation-pass-data#Safe-args
答案 4 :(得分:3)
NavigationUI.setupActionBarWithNavController(this, navController)
请不要忘记为导航图中的片段指定 android:label 。
后退:
override fun onSupportNavigateUp(): Boolean {
return NavigationUI.navigateUp(navController, null)
}
答案 5 :(得分:2)
答案 6 :(得分:1)
如今,使用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
。
答案 7 :(得分:0)
我建议在每个屏幕中包含AppBar。 为避免代码重复,创建一个帮助程序,以标题为参数构建AppBar。然后在每个屏幕类中调用帮助程序。
答案 8 :(得分:0)
由于其他人仍在参与回答这个问题,所以我要回答我自己的问题,因为此后API发生了变化。
首先,从android:label
(又称“导航图”)内部,从要更改标题的片段中删除navigation.xml
。
现在,您可以通过调用
从Fragment
更改标题
(requireActivity() as MainActivity).title = "My title"
但是您应该使用的首选方式是MainActivity
中的API NavController.addOnDestinationChangedListener
。一个例子:
NavController.OnDestinationChangedListener { controller, destination, arguments ->
title = when (destination) {
R.id.someFragment -> "My title"
else -> "Default title"
}
// if (destination == R.id.someFragment) {
// title = "My title"
// } else {
// title = "Default Title"
// }
}
答案 9 :(得分:0)
另一种方法可能是:
fun Fragment.setToolbarTitle(title: String) {
(activity as NavigationDrawerActivity).supportActionBar?.title = title
}
答案 10 :(得分:0)
在导航图xml文件中删除detatil片段的标签。
然后通过参数将首选标题传递给需要标题的目标片段,如下所示。
第一个片段代码-起点
findNavController().navigate(
R.id.action_FirstFragment_to_descriptionFragment,
bundleOf(Pair("toolbar_title", "My Details Fragment Title"))
)
如您所见,导航到“目标片段”时我在参数束中成对发送
因此,在您的目标片段中,从像这样的onCreate方法中的参数获取标题
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
toolbarTitle = requireArguments().getString("toolbar_title", "")
}
requireActivity().toolbar.title = toolbarTitle
答案 11 :(得分:0)
使用导航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
})
答案 12 :(得分:0)
无论如何,我尝试过的这些答案中有一些不起作用,然后我决定使用界面在 Kotlin 中以旧的 Java 方式进行
创建如下所示的界面。
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")
}
答案 13 :(得分:0)
我花了几个小时试图做一件非常简单的事情,例如在从主片段上的特定项目导航时更改细节片段的栏标题。我爸爸总是对我说: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);
}
答案 14 :(得分:0)
简单的解决方案:
布局
backend
活动
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
答案 15 :(得分:0)
如果您想通过 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
}
}