Android导航组件:在片段中传递值(参数)

42

我所做的事情:

我创建了导航抽屉活动,根据新的Android架构更新了新格式的导航抽屉活动,并且使用导航组件结构。

以下是NavigationView代码与NavControllerNavigationUI当我点击任何导航项时打开片段。

DrawerLayout drawer = findViewById(R.id.drawer_layout);
NavigationView navigationView = findViewById(R.id.nav_view);
// Passing each menu ID as a set of Ids because each
// menu should be considered as top level destinations.
mAppBarConfiguration = new AppBarConfiguration.Builder(
        R.id.nav_home, R.id.nav_profile, R.id.nav_privacy_policy,
        R.id.nav_terms, R.id.nav_contact_us, R.id.nav_share, R.id.nav_send)
        .setDrawerLayout(drawer)
        .build();
NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
NavigationUI.setupActionBarWithNavController(this, navController, mAppBarConfiguration);
NavigationUI.setupWithNavController(navigationView, navController);

这是关于 nav_host_fragment 的内容:
<fragment
    android:id="@+id/nav_host_fragment"
    android:name="androidx.navigation.fragment.NavHostFragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    app:defaultNavHost="true"
    app:layout_constraintLeft_toLeftOf="parent"
    app:layout_constraintRight_toRightOf="parent"
    app:layout_constraintTop_toTopOf="parent"
    app:navGraph="@navigation/mobile_navigation" />

导航是使用这个navigation/mobile_navigation.xml文件进行的。
<?xml version="1.0" encoding="utf-8"?>
<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/nav_home">

    <fragment
        android:id="@+id/nav_home"
        android:name="com.sohamerp.marsremedies.fragment.HomeFragment"
        android:label="@string/menu_home"
        tools:layout="@layout/fragment_home" />

    <fragment
        android:id="@+id/nav_profile"
        android:name="com.sohamerp.marsremedies.fragment.ProfileFragment"
        android:label="@string/menu_my_profile"
        tools:layout="@layout/fragment_profile" />

    <fragment
        android:id="@+id/nav_privacy_policy"
        android:name="com.sohamerp.marsremedies.fragment.PrivacyPolicyFragment"
        android:label="@string/menu_privacy_policy"
        tools:layout="@layout/fragment_privacy_policy" />

    <fragment
        android:id="@+id/nav_terms"
        android:name="com.sohamerp.marsremedies.fragment.TermsConditionFragment"
        android:label="@string/menu_terms"
        tools:layout="@layout/fragment_terms_condition" />

    <fragment
        android:id="@+id/nav_contact_us"
        android:name="com.sohamerp.marsremedies.fragment.ContactUsFragment"
        android:label="@string/menu_contact_us"
        tools:layout="@layout/fragment_terms_condition" />

</navigation>

我想做什么:

现在我想在调用Fragment时作为捆绑包(参数)传递一些值。

场景:我有两个片段PrivacyPolicyFragmentTermsConditionsFragment,在这两个片段中,我只是根据需要在WebView中打开链接。因此,当我点击隐私政策菜单项时,我将传递相关的链接。

在这种新结构navigation/mobile_navigation.xml打开片段时,如何传递参数?

有什么帮助吗?


请使用https://developer.android.com/guide/navigation/navigation-pass-data。 - coroutineDispatcher
12个回答

36

简单又快速的解决方案:

在不同目的地之间传递参数

Bundle bundle = new Bundle();
bundle.putString("amount", amount);
Navigation.findNavController(view).navigate(R.id.confirmationAction, bundle);

接收

TextView tv = view.findViewById(R.id.textViewAmount);
tv.setText(getArguments().getString("amount"));

简单的解决方案,附带清晰的示例,谢谢。 - orchidrudra

27

因此,我忘记了访问这个链接:Define Destination Arguments

但是这个答案对于像我这样的懒人很有帮助:

在项目级别的build.gradle中添加依赖项:

classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.1.0"
在应用级别的 build.gradle 中应用插件。
apply plugin: "androidx.navigation.safeargs"

使用XML:预定义(静态)值:

在导航的XML文件/navigation/mobile_navigation.xml中,将argument标记声明如下,或者您可以通过此链接进行设计:

<fragment
    android:id="@+id/nav_privacy_policy"
    android:name="com.sohamerp.marsremedies.fragment.PrivacyPolicyFragment"
    android:label="@string/menu_privacy_policy"
    tools:layout="@layout/fragment_privacy_policy" >
    <argument
        android:name="privacyPolicyLink"
        app:argType="string"
        android:defaultValue="http://sohamerp.com/avo/avo_privacy_policy.html"/>
</fragment>

<fragment
    android:id="@+id/nav_terms"
    android:name="com.sohamerp.marsremedies.fragment.PrivacyPolicyFragment"
    android:label="@string/menu_terms"
    tools:layout="@layout/fragment_terms_condition" >
    <argument
        android:name="privacyPolicyLink"
        app:argType="string"
        android:defaultValue="http://sohamerp.com/avo/avo_privacy_policy.html"/>
</fragment>

现在您需要在Fragment中编写代码,比如:

if(getArguments() != null) {
    // The getPrivacyPolicyLink() method will be created automatically.
    String url = PrivacyPolicyFragmentArgs.fromBundle(getArguments()).getPrivacyPolicyLink();
}

希望这能对其他人有所帮助。


或者,为了生成适用于仅 Kotlin 模块的 Kotlin 代码,请添加:apply plugin: "androidx.navigation.safeargs.kotlin" - Anoop M Maddasseri
1
我们可以使用Bundle来传递数据吗? - sophin
5
你如何传递数据?例如从这里:Navigation.findNavController(view).navigate(R.id.action); - G. Ciardini
@sophin 从文档中得知:"强烈建议使用Safe Args进行导航和数据传递,因为它确保了类型安全。在某些情况下,例如如果您没有使用Gradle,则无法使用Safe Args插件。在这些情况下,您可以使用Bundles直接传递数据。" - MGLabs
我该如何通过Safe Args从一个片段传递imageView到另一个片段? - FrensisXh
如果我想从Activity向当前Fragment传递新的Bundle,该怎么办?我知道我们可以像下面这样做,但是是否还有其他方法可以不弹出返回堆栈并重新导航?谢谢! { val navController = findNavController(R.id.nav_host_fragment) val id = navController.currentDestination?.id navController.popBackStack(id!!, true) navController.navigate(id, bundle) } - Huigege

14

在这种情况下,您可以使用

  private NavController navController = Navigation.findNavController(this, R.id.nav_host_fragment);
  // Create the Bundle to pass, you can put String, Integer, or serializable object
  Bundle bundle = new Bundle();
  bundle.putString("link","http://yourlink.com/policy");
  bundle.putSerializable("USER", user); // Serializable Object
  navController.navigate(R.id.nav_terms, bundle); // called fragment with agruments

如果需要帮助,请回复它。


3
如何获取R.id.nav_terms中的bundle? - Mahmoud Hadad
4
在接下来的代码片段中,在onCreateView方法中调用"Bundle arguments = getArguments();"语句。 如果"arguments"不为空,执行花括号中的代码。 - Vivek Hande
在 Kotlin 中,如果没有 Bundle ,requireArguments() 会抛出 IllegalStateException 异常,因此我不得不使用 catch 块。 - Mahmoud Hadad
1
除了 @Vivek 的回复之外,在 if 语句中,您需要执行类似于 arguments.get("KEY_ID") 的操作。 例如: if (arguments != null) { user = (User) arguments.get("User"); } - hfunes.com

9

要向其他片段/目的地传递参数,请使用Safe Args,以确保类型安全。就像@bromden所示,Safe Args将为每个片段/目的地生成一个类,其中包含action的起点。然后,您可以将参数传递到导航到片段的action中。

在接收片段中,例如PrivacyFragment(如果您的代码是在Kotlin中),请使用navArgs()属性委托来访问这些参数。例如:

val args: PrivacyFragmentArgs by navArgs()

为了更好地理解这一点,请访问目的地之间传递数据


4
在较新版本的Android Studio 3.2+中,需要在两个build.gradle文件中添加以下依赖项和插件。
步骤1: 在 Project-Level build.gradle 文件中添加依赖项。
dependencies {
    classpath 'androidx.navigation:navigation-safe-args-gradle-plugin:2.3.5'
}

应用级别的build.gradle中应用插件
plugins {
    id 'androidx.navigation.safeargs' 
}

步骤二

  1. 在导航文件中,即 res/navigation/nav_graph.xml
  2. 在任何一个片段或内部片段中使用操作标签声明参数标签
  3. 列表项

示例 XML 代码

 <fragment
     android:id="@+id/nav_register"
     android:name="com.pd.demo.ui.profile.RegisterFragment"
     android:label="@string/title_register"
     tools:layout="@layout/fragment_register">
     <action
         android:id="@+id/action_nav_register_to_nav_verify_otp"
         app:destination="@id/nav_verify_otp">
         <argument
             android:name="mobile"
             app:argType="string" />
         <argument
             android:name="password"
             app:argType="string" />
     </action>
 </fragment>

第三步

以下是Kotlin代码,将参数传递给目标片段。

val bundle = bundleOf("mobile" to binding.etMobileNo.text.toString().trim())
Navigation.findNavController(binding.root).navigate(R.id.action_nav_register_to_nav_verify_otp, bundle)

步骤4

以下是 Kotlin 代码,用于从源片段获取 bundle 参数:

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    mobileNo = arguments!!.getString("mobile").toString()
    password = arguments!!.getString("password").toString()
}

这段代码将有所帮助。


2
您可以实现NavigationView.OnNavigationItemSelectedListener,并像这样执行操作:
 override fun onNavigationItemSelected(item: MenuItem): Boolean {
   drawer_layout.closeDrawers()

        if (item.itemId == nv_navigation_drawer_navigation_view.checkedItem?.itemId)
            return false

     Handler().postDelayed({
                when (item.itemId) {
                    R.id.nav_privacy_policy -> {
                           val action = FragmentDirections.actionFragmentToPrivacyFragment("Policy link")

                     findNavController().navigate(action)

                    }
                }
            }, DRAWER_NAVIGATION_DELAY)
  return true
    }

在XML中,您可以向接收片段添加参数,在这种情况下。
   <fragment
    android:id="@+id/nav_privacy_policy"
    android:name=".fragment.PrivacyPolicyFragment"
    android:label="@string/menu_privacy_policy"
    tools:layout="@layout/fragment_privacy_policy">

    <argument
        android:name="policy"
        app:argType="string" />
</fragment>

你能回答这个问题吗?https://dev59.com/wbjna4cB1Zd3GeqP6jl6 - Pratik Butani
这里的FragmentDirections是什么? - Pratik Butani
FragmentDirections 是由 SafeArgs 生成的类。 - bromden

1

使用NavController NavGraph导航从起始目的地传递数据非常简单。我使用它来显示与订单头相关的订购行:

    private void showRepositionLinesFragment(AppObjects.RepOrderHeader orderHeader) {

    int number = orderHeader.getOrderNumber();
    String orderNumber = String.format("%06d",number);
    String createDate = orderHeader.getCreateDate();
    Globals.LogTrace(this, AppAlertDialog.DialogType.Info,
        "Navigate to FragRepoLines with orderNumber: " + orderNumber,false);
    NavController navController = NavHostFragment.findNavController(FragmentRepositionHeaders.this);
    Bundle bundle = new Bundle();
    bundle.putString(getString(R.string.arg_header_ordernumber),orderNumber);
    bundle.putString(getString(R.string.arg_repheader_createdate), createDate);
    navController.getGraph().findNode(R.id.FragRepoLines).setLabel(orderNumber + " " + createDate);
    navController.navigate(R.id.action_FragRepoHeaders_to_FragRepoLines,bundle);
}

从处理订单行的片段中获取数据变得更加复杂。我花了几个小时尝试使用NavController getArguments()。最终,这是对我有效的方法。
在起始片段中:
    NavController navController = NavHostFragment.findNavController(this);
    // We use a String here, but any type that can be put in a Bundle is supported
    MutableLiveData<String> liveData = navController.getCurrentBackStackEntry()
        .getSavedStateHandle()
        .getLiveData(getString(R.string.arg_header_ordernumber));
    liveData.observe(getViewLifecycleOwner(), new Observer<String>() {
        @Override
        public void onChanged(String s) {
            Globals.LogTrace(this, AppAlertDialog.DialogType.Info, "+++++++++  liveData changed -> " + s, false);
        }
    });

在目标片段中:
    String arg = getString(R.string.arg_header_ordernumber);
    NavController navController = NavHostFragment.findNavController(this);
    NavBackStackEntry navBackStackEntry = navController.getCurrentBackStackEntry();
    if (navBackStackEntry != null) {
        SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle();
        if (savedStateHandle != null) {
            savedStateHandle.set(arg, "000000");
        } else {
            Globals.LogTrace(this, AppAlertDialog.DialogType.Info,"savedStateHandle == null",false);
        }
    } else {
        Globals.LogTrace(this, AppAlertDialog.DialogType.Info,"navBackStackEntry == null",false);
    }

来源: 与导航组件进行编程交互

我将navController.getPreviousBackStackEntry()更改为navController.getCurrentBackStackEntry()


1

你也可以传递可序列化的对象、枚举值和基本类型的数组。例如:

enum class ObjectType : Serializable {
    FIRST, SECOND
}

然后,将参数添加到xml中。
<fragment
        android:id="@+id/nav_profile"
        android:name="com.sohamerp.marsremedies.fragment.ProfileFragment"
        android:label="@string/menu_my_profile"
        tools:layout="@layout/fragment_profile" >

        <argument
            android:name="myObjectType"
            android:defaultValue="SECOND"
            app:argType="com.project.app.data.ObjectType" />
</fragment>

请注意,您应该指定完整路径!

1

关于片段 Uri(String) 的示例(Kotlin):

第一个片段:

            val args = Bundle()
            args.putString("uri", imageuri)
            findNavController()
            .navigate(R.id.action_FirstFragment_to_SecondFragment, args)

第二个片段:

        val uri = arguments?.getString("uri")
        Toast.makeText(requireContext(), "URI: $uri", Toast.LENGTH_SHORT).show()

0

片段1

 Bundle bundle=new Bundle();
  bundle.putInt("id",1);
  Fragment fragment=Fragment1.this; //current fragment name
  fragment.setArguments(bundle);
  NavHostFragment.findNavController(fragment)
  .navigate(R.id.action_FirstFragment_to_SecondFragment,bundle);
  //1st arug is navigation action app:destination name defined in nav.graph 

片段2

public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    binding = SecondFragmentBinding.inflate(inflater, container, false);
    Bundle bundle=getArguments();
    Integer id=bundle.getInt("id");
    Toast.makeText(requireContext() , id, Toast.LENGTH_SHORT).show();});

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