如何使用导航架构组件在应用栏中设置标题

时间:2018-09-26 06:18:17

标签: android android-layout androidx android-architecture-navigation

我正在尝试使用导航架构组件,现在在设置标题时遇到了困难。如何以编程方式设置标题以及其工作方式?

为解决我的问题,让我们举个例子,在这里,我建立了一个简单的应用,其中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

那么使用导航组件显示片段标题的正确方法是什么?

16 个答案:

答案 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)

自提出问题以来,API可能已更改,但是现在您可以在导航图标签中的应用资源中指示对字符串的引用。

enter image description here

如果要为片段使用静态标题,这将非常有用。

答案 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", "")
    }

然后使用它在这样的onCreateView方法中更改Main Activity的标题
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
    }

}