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

Raj*_*shi 10 android android-layout android-architecture-navigation androidx

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

为了澄清我的问题,让我们举个例子,在这里,我建立了一个简单的应用程序,用于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();
    }
}
Run Code Online (Sandbox Code Playgroud)

主片段

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));
    }

}
Run Code Online (Sandbox Code Playgroud)

详细片段

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

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>
Run Code Online (Sandbox Code Playgroud)

因此,当启动我的应用程序时,标题为“ MainActivity”。与往常一样,它显示MainFragment包含要转到的按钮DetailFragment。在中,DialogFragment我将标题设置为:

getActivity().getSupportActionBar().setTitle("Detail");
Run Code Online (Sandbox Code Playgroud)

第一个问题:因此,单击MainFragmentgoto 上的按钮DetailFragment,它确实到达了那里,标题变为“ Detail”。但是,单击后退按钮后,标题将更改为“ 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");
}
Run Code Online (Sandbox Code Playgroud)

现在从在返回回DetailFragmentMainFragment标题更改为“你好”。但这是第二个问题,当我关闭应用程序并重新启动时,标题改回“ MainActivity”,尽管它应该显示“ Hello”,知道吗?

好吧,然后添加setTitle("Hello")MainFrgment不工作了。例如,活动开始,标题为“ Hello”,转到DetailsFragment并再次按返回按钮,标题回到“ fragment_main”。

唯一的解决方案是同时setTitle("Hello")使用Navigation.findNavController(view).getCurrentDestination().setLabel("Hello")in和MainFragment

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

Ali*_*ran 15

如果您未指定应用栏(默认应用栏),则可以在片段中使用此代码

(activity as MainActivity).supportActionBar?.title = "Your Custom Title"
Run Code Online (Sandbox Code Playgroud)

记得删除android:label导航图中的属性

快乐码^-^


ʍѳђ*_*ઽ૯ท 12

实际上是因为:

android:label="fragment_main"
Run Code Online (Sandbox Code Playgroud)

您已在xml中设置的内容。

那么,Fragment使用Navigation Component 显示s 标题的正确方法是什么?

setTitle()在这一点上工作。但是,由于您为这些设置了标签Fragment,因此在重新创建时可能会再次显示标签Activity。解决方案可能是删除android:label,然后使用代码来处理您的事情:

((AppCompatActivity) getActivity()).getSupportActionBar().setTitle("your title");
Run Code Online (Sandbox Code Playgroud)

要么:

((AppCompatActivity) getActivity()).getSupportActionBar().setSubtitle("your subtitle");
Run Code Online (Sandbox Code Playgroud)

在中onCreateView()


找到了解决方法:

interface TempToolbarTitleListener {
    fun updateTitle(title: String)
}

class MainActivity : AppCompatActivity(), TempToolbarTitleListener {

    ...

    override fun updateTitle(title: String) {
        binding.toolbar.title = title
    }
}
Run Code Online (Sandbox Code Playgroud)

然后:

(activity as TempToolbarTitleListener).updateTitle("custom title")
Run Code Online (Sandbox Code Playgroud)

也请检查以下内容:使用AndroidX导航的片段中的动态ActionBar标题

  • 所以,导航组件有问题。 (2认同)

小智 12

根据经验,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"
        }

    }
Run Code Online (Sandbox Code Playgroud)

  • 对于像我一样愚蠢并想知道为什么它不起作用的其他人:您必须在底部添加“supportActionBar?.title = title”以将标题应用到操作栏 (3认同)

fra*_*kee 12

现在使用 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()
}
Run Code Online (Sandbox Code Playgroud)

基本上就是这样。不要忘记android:label在导航图中指定。


Raj*_*shi 6

由于其他人仍在参与回答此问题,因此自此之后API发生了变化,让我回答我自己的问题

首先,android:label从内部navigation.xml(即导航图)中,从您想要更改标题的片段中删除。

现在您可以Fragment通过调用更改标题

(requireActivity() as MainActivity).title = "My title"
Run Code Online (Sandbox Code Playgroud)

但是,你应该使用的最佳方法是API NavController.addOnDestinationChangedListener从内部MainActivity。一个例子:

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"        
//    }
}
Run Code Online (Sandbox Code Playgroud)

  • 即使从导航图中删除标签后,“(requireActivity() as MainActivity).title”也不会更改标签。也许 API 功能最近发生了变化。 (2认同)

Ale*_*hen 5

您可以使用导航图 xml 文件并将片段的标签设置为参数。然后,在您的父片段中,您可以使用 SafeArgs 传递参数(请按照https://developer.android.com/guide/navigation/navigation-pass-data#Safe-args上的指南设置 SafeArgs)和提供默认值以避免标题为 null 或为空。

<!--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>
Run Code Online (Sandbox Code Playgroud)

在原始片段中:

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);
}
Run Code Online (Sandbox Code Playgroud)

FragmentADirections 和 FragmentBAction - 这些类是根据您的 nav_graph.xml 文件中的 ID 自动生成的。在“您的操作 ID + 操作”类型类中,您可以找到自动生成的 setter 方法,您可以使用这些方法传递参数

在您的接收目的地,您调用自动生成的类 {您的接收片段 ID}+Args

FragmentBArgs.fromBundle(requireArguments()).getMyTitle();
Run Code Online (Sandbox Code Playgroud)

请参考官方指南 https://developer.android.com/guide/navigation/navigation-pass-data#Safe-args


Sum*_*kla 5

NavigationUI.setupActionBarWithNavController(this, navController)
Run Code Online (Sandbox Code Playgroud)

不要忘记在导航图中为片段指定android:label

要返回:

override fun onSupportNavigateUp(): Boolean {
    return NavigationUI.navigateUp(navController, null)
}
Run Code Online (Sandbox Code Playgroud)


loo*_*han 5

我花了几个小时尝试做一件非常简单的事情,例如从主片段上的特定项目导航时更改细节片段的栏标题。我爸爸总是对我说:KISS,保持简单,儿子。setTitle() 崩溃了,界面很麻烦,针对最新的片段导航实现,在代码行方面提出了一个(非常)简单的解决方案。在主片段中解析 NavController,获取 NavGraph,找到目标节点,设置标题,最后但并非最不重要的导航到它:

//----------------------------------------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

私人无效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);
Run Code Online (Sandbox Code Playgroud)

}