如何使用底部导航视图和Android Navigation组件将参数传递给一个片段?

25

使用底部导航视图和Navigation组件,在片段中传递和访问参数是否可行?

我正在使用一种活动和多个片段的方法,其中我的顶级片段需要一个参数(通常通过newInstance生成的方法完成)。我查看了Navigation组件开发人员指南和codelab,但它仅提到使用safeargs并在目标和操作中添加参数标记。

这是我的导航图:

<navigation xmlns:app="http://schemas.android.com/apk/res-auto" 
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools" 
    app:startDestination="@id/homeFragment">

    <fragment android:id="@+id/homeFragment"
          android:name="uk.co.homeready.homeready.HomeFragment"
          android:label="fragment_home"
          tools:layout="@layout/fragment_home">
          <!--Do I create an argument block here?-->
    </fragment>

    <fragment android:id="@+id/calculatorFragment"
          android:name="uk.co.homeready.homeready.CalculatorFragment"
          android:label="fragment_calculator"
          tools:layout="@layout/fragment_calculator"/>

    <fragment android:id="@+id/resourcesFragment"
          android:name="uk.co.homeready.homeready.ResourcesFragment"
          android:label="fragment_resources"
          tools:layout="@layout/fragment_resources"/>

</navigation>

底部导航视图菜单:

<menu xmlns:android="http://schemas.android.com/apk/res/android">

    <item
        android:id="@+id/homeFragment"
        android:icon="@drawable/ic_home_black_24dp"
        android:title="@string/title_home"/>

    <item
        android:id="@+id/calculatorFragment"
        android:icon="@drawable/ic_baseline_attach_money_24px"
        android:title="@string/title_calculator"/>

    <item
        android:id="@+id/resourcesFragment"
        android:icon="@drawable/ic_baseline_library_books_24px"
        android:title="@string/title_resources"/>

</menu>

MainActivity:

主活动:
override fun onCreate(savedInstanceState: Bundle?) {
        ...
        val navController = Navigation.findNavController(this, 
        R.id.nav_host_fragment)
        bottom_navigation.setupWithNavController(navController)
        ....
}

主活动页面.xml

<android.support.constraint.ConstraintLayout>
    <fragment
        android:id="@+id/nav_host_fragment"
        android:name="androidx.navigation.fragment.NavHostFragment"
        app:layout_constraintBottom_toTopOf="@id/bottom_navigation"
        app:defaultNavHost="true"
        app:navGraph="@navigation/nav_graph"/>

    <android.support.design.widget.BottomNavigationView
        android:id="@+id/bottom_navigation"
        app:menu="@menu/bottom_navigation"/>

</android.support.constraint.ConstraintLayout>

主页片段

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    val argument = //TODO access argument here
    ...
}

所以你想让你的 homeFragment 在从底部导航栏中点击它时始终带有一些默认的参数吗?你是否在其他地方重复使用了 homeFragment 并使用不同的参数,还是它们总是固定的值? - ianhanniballake
对于我的用例,当您点击底部导航时,我想要传递一些默认设置的参数。同时,我也可能会在其他地方重复使用homeFragment。 - skon
4个回答

31

如果我理解正确,您想将参数传递给与菜单项绑定的目标。尝试在您的活动的onCreate方法中使用'OnDestinationChangedListener',类似于这样:

navController.addOnDestinationChangedListener { controller, destination, arguments ->
        when(destination.id) {
            R.id.homeFragment -> {
                val argument = NavArgument.Builder().setDefaultValue(6).build()
                destination.addArgument("Argument", argument)
            }
        }
    }

更新:

如果您希望您的起始目的地接收默认参数,则实现方式应不同。 首先,从您的“NavHostFragment” xml 标签中删除“app:navGraph =“@navigation/nav_graph””。

然后,在您的 activity onCreate 内部,您需要填充该图形:

 val navInflater = navController.navInflater
 val graph = navInflater.inflate(R.navigation.nav_graph)

然后将您的参数添加到图表中(这些参数将附加到起始目的地)

val navArgument1=NavArgument.Builder().setDefaultValue(1).build()           
val navArgument2=NavArgument.Builder().setDefaultValue("Hello").build()
graph.addArgument("Key1",navArgument1)
graph.addArgument("Key2",navArgument2)

将图表附加到NavController:

navController.graph=graph
现在你的第一个目的地应该接收附加参数。

1
谢谢你的回答,Alex。我正在通过 val argument = arguments?.getString("Argument") 在 HomeFragment 中检索参数。然而,在应用程序启动时,即使我可以看到监听器被调用并且参数在主活动中被创建,HomeFragment 中的 argument 仍然为 null。我必须点击底部导航栏中的另一个项目,然后再次点击并导航回 HomeFragment 才能返回 argument 的值。 - skon
2
OnDestinationChangedListener 仅在第二次调用时起作用的原因是因为它在 navigate() 调用完成并为第一个 Fragment 设置参数后才被调用。将 <argument> 添加到图表中等同于在运行时手动创建 NavArgument,这是推荐的方法。 - ianhanniballake
3
我尝试了第一种方法,但OnDestinationChangedListener在第一次使用时无效。 - Shrikant
@ianhanniballake 我也遇到了同样的问题,即 OnDestinationChangedListener 第一次不起作用。我已经通过 xml 将我的参数添加到目标中,但仍然没有起作用。有什么想法吗? - Khodor

7
正确的方式是在目标位置使用<argument>块。
<fragment android:id="@+id/homeFragment"
      android:name="uk.co.homeready.homeready.HomeFragment"
      android:label="fragment_home"
      tools:layout="@layout/fragment_home">
      <argument
          android:name="Argument"
          android:defaultValue="value"
          />
</fragment>

这将自动填充Fragment的参数为默认值,无需任何额外的代码。从Navigation 1.0.0-alpha09开始,无论您是否使用Safe Args Gradle插件,这都是正确的。


1
我该如何在目标片段中接收这些参数? - Nauman Ash
1
@NaumanAsh - 无论您是通过Safe Args还是其他方式接收参数,都在文档中有详细介绍。 - ianhanniballake
@ianhanniballake 当从bottomNavView中点击一个项目时,我们能否以编程的方式更改参数值? - Zain
这些参数是从哪里填充的?主活动吗? - Mattwmaster58
@Mattwmaster58 - android:defaultValue 表示无论您如何导航到该片段,此参数始终会被添加。 - ianhanniballake

3

默认值对我来说并不可行,因为我的动态菜单项可能会有多个相同的目标但参数不同。(从服务器变更)

实现BottomNavigationView.OnNavigationItemSelectedListener:

override fun onNavigationItemSelected(item: MenuItem): Boolean {
    val fragmentId = item.itemId
    val arguments = argumentsByFragmentId[fragmentId] // custom mutableMapOf<Int, Bundle?>() with arguments
    navController().navigate(fragmentId, arguments)
    return true
}

要使用它,您需要接管导航,并替换监听器。以下是调用顺序:

bottomNavigationView.setupWithNavController(navController)
bottomNavigationView.setOnNavigationItemSelectedListener(this)

0
我的论点已经在导航图XML中定义了,不确定为什么,但是@Alex的答案对我没有起作用,相反,我做了这个(从destinationChangedListener):
 arguments?.putParcelable("argname", argvalue)

注意,我之前使用这个功能是为了在深度链接到特定的导航栏选项卡时提供特定的响应。默认情况下,我还需要这样做(以避免重复使用相同的捆绑包并再次进行深度链接处理)。
arguments?.remove("argname")

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接