如果我在XML布局中声明了一个片段,如何向其传递Bundle?

81

我有一个活动,已经用一个碎片(fragment)替换了它。该活动会接收一个带有一些额外信息的意图(Intent),以便活动能够显示相应数据。

现在我的活动只是一个包装器(wrapper),围绕着执行相同工作的碎片(fragment)。如果我在XML中使用标签声明碎片(fragment),那么如何将这个数据包(bundle)传递给碎片(fragment)呢?

如果我使用FragmentTransaction将碎片(fragment)放入ViewGroup中,我会有机会在碎片(fragment)构造函数中传递这些信息,但我想知道在定义碎片(fragment)的情况下,该怎么做。


请尝试访问以下链接:https://dev59.com/NWoy5IYBdhLWcg3wScTb#17674292 - Ashish Dwivedi
非常简单,看看@DanieleSegato的优秀答案就知道了。 - Fattie
8个回答

54
现在我的Activity只是一个包装Fragment的容器,如果我使用标记在XML中声明了该Fragment,那么我如何将bundle传递给Fragment呢?
你做不到。
但是,您可以在FragmentManager上调用findFragmentById()来检索片段后再次填充它,然后调用某些方法来关联数据。虽然显然无法使用setArguments()进行关联,但您的片段可以通过其他方式(例如onSaveInstanceState(),setRetainInstance(true)等)安排保留数据以通过配置更改持续一段时间。

当我提出这个问题时,我决定走另一条路。但就在今天,我遇到了类似的情况,回到了这篇文章。我想试试。setArguments解决方案似乎不起作用: 10-24 12:48:33.276: E/AndroidRuntime(21417): Caused by: java.lang.IllegalStateException: Fragment already active 尝试只调用片段上的方法。 - Plantage
3
这不应该被标记为正确的。它是错误的。根据片段文档(http://developer.android.com/reference/android/app/Fragment.html#setArguments(android.os.Bundle),必须在片段附加到活动之前调用setArguments()。如果你可以通过findFragmentById()找到片段,那么片段已经被附加了。请参见https://dev59.com/KXvaa4cB1Zd3GeqPINS4?lq=1获取正确的解决方案。 - Neil Sainsbury
@Neil:我能理解你困惑的原因。我进行了小的编辑,以澄清“setArguments()”调用的时间。 - CommonsWare
@Neil:你说得对,我的编辑是错误的。提醒我以后不要用手机编辑答案。然而,你链接的答案是无关的,因为它也没有解决片段膨胀的问题,除了说你不能使用它。 - CommonsWare
6
参见https://dev59.com/uWMl5IYBdhLWcg3wyJfz,了解如何将数据传递给在XML中定义的Fragment的选项。 - TheIT
显示剩余4条评论

50

这并不是一种封装的方式,但我最终从父活动中“拉取”了捆绑包:

Bundle bundle = getActivity().getIntent().getExtras();

25

如果您通过XML而非编程方式来填充Fragment,则无法传递Bundle参数,但是可以通过XML将参数(或者更确切地说,属性)传递给Fragment。

这个过程类似于定义View自定义属性的方式。 除了Android Studio(目前)不会为此过程提供帮助。

假设这是您的Fragment使用参数的方式(我将使用Kotlin,但Java也完全可行):

class MyFragment: Fragment() {

    // your fragment parameter, a string
    private var screenName: String? = null

    override fun onAttach(context: Context?) {
        super.onAttach(context)
        if (screenName == null) {
            screenName = arguments?.getString("screen_name")
        }
    }
}

你想要做类似这样的事情:

<fragment
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/myFragment"
    android:name="com.example.MyFragment"
    app:screen_name="@string/screen_a"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

请注意 app:screen_name="@string/screen_a"

为了使其正常工作,只需在值文件(fragment_attrs.xml 或选择任何你想要的名称)中添加此内容:

<!-- define your attribute name and type -->
<attr name="screen_name" format="string|reference"/>

<!-- define a bunch of constants you wanna use -->
<string name="screen_a" translatable="false">ScreenA</string>
<string name="screen_b" translatable="false">ScreeenB</string>

<!-- now define which arguments your fragment is gonna have (can be more then one) -->
<!-- the convention is "FragmentClassName_MembersInjector" -->
<declare-styleable name="MyFragment_MembersInjector">
    <attr name="screen_name"/>
</declare-styleable>

快完成了,您只需在片段中读取它,因此请添加该方法:

override fun onInflate(context: Context?, attrs: AttributeSet?, savedInstanceState: Bundle?) {
    super.onInflate(context, attrs, savedInstanceState)
    if (context != null && attrs != null && screenName == null) {
        val ta = context.obtainStyledAttributes(attrs, R.styleable.MyFragment_MembersInjector)
        if (ta.hasValue(R.styleable.MyFragment_MembersInjector_screen_name)) {
            screenName = ta.getString(R.styleable.MyFragment_MembersInjector_screen_name)
        }
        ta.recycle()
    }
}

嘿,你的片段中有XML属性了 :)

限制:

  • Android Studio(截至目前)无法在布局XML中自动完成此类参数
  • 您只能传递可定义为Android属性的内容,而不能传递Parcelable

这正是我所需要的。非常感谢你。 - bikram
救星,正是我正在寻找的。 - Yuvraj Pandey
哇,我做了10年的Android编程,从来没有想到过这一点。我只希望我不会滥用它,因为它真的很方便。 - TheRealChx101

15

另一个选项是在XML中不声明片段。我知道这不完全是你想做的。但是,您可以在视图中声明一个简单的布局,如下所示:

    <LinearLayout
        android:id="@+id/fragment_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical" />

然后在你的Activity类中,你可以通过编程方式填充布局与片段。这样你就可以使用参数传递参数。

FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
MyFragment fragment = MyFragment.newInstance();
Bundle args = new Bundle();
args.putInt(Global.INTENT_INT_ROLE, 1);
fragment.setArguments(args);
fragmentTransaction.add(R.id.fragment_container, fragment, "MyActivity");
fragmentTransaction.commit();

在这个片段中,

if (getArguments() != null) {
   int role = getArguments().getInt(Global.INTENT_INT_ROLE); }

这种方法并不像在xml中声明那样干净简单,但是我已经转向它,因为它可以让你更加控制该片段。


这是一种有点“那就别那样做”的方法,但对我来说比通过单例对象交换数据的想法更有意义(正如另一个答案所建议的)。 - Konrad Morawski
如果单例对象没有由持久化数据库支持,那么Android可能会在后台销毁您的应用程序进程,并尝试从片段参数或包中稍后重新创建其先前的状态。单例不会被恢复,应用程序状态将丢失,这会导致糟糕的用户体验 - 请尝试通过使用片段参数或通过数据库备份来避免这样做。 - A. Steenbergen
你的回答太有帮助了!!!!!!!哇啊啊啊啊!https://tenor.com/search/thank-you-anime-gifs - Fattie
@A.Steenbergen 当然可以,但通常的情况是如果参数不存在,则执行一些默认操作;如果它们存在,则使用它们。 - Fattie

9
我知道现在回答有点晚,但我认为仍然有人需要这个答案 :)
只需在活动中覆盖onAttachFragment()即可。
@Override
public void onAttachFragment(Fragment fragment)
{
    super.onAttachFragment(fragment);

    if (fragment.getId() == R.id.frgBlank)
    {
        Bundle b = new Bundle();
        b.putString("msg", "Message");

        fragment.setArguments(b);
    }
}

在片段的 onCreateView 方法中
Bundle b = getArguments();
if (b != null)
{
    Toast.makeText(getBaseContext(), b.getString("msg"), Toast.LENGTH_SHORT).show();
}

4
我所看到的唯一解决方案是不要使用参数作为数据交换渠道。相反,使您的片段从其他地方获取必要的信息。回调以获取适当的活动,咨询临时存储器,单例对象等。
另一个有用的解决方案是使用允许不相关对象通过中介者设计模式交换消息的框架,如Otto

0

这种方法对我很有效。

你不能从任何地方传递Bundle,但是你可以在片段的onAttach方法中设置参数。

稍后在片段的生命周期方法中,你可以使用这些Bundle参数。

override fun onAttach(context: Context) {
        super.onAttach(context)
        if(arguments == null){
            val bundle = Bundle()
            bundle.putString("mykey", "myvalue")
            arguments = bundle
        }
    }

有人可能会问,为什么我们可以在可用的地方直接使用值而要将参数设置在片段中。这是对的,但是当您将这些参数传递给其他类(比如任何视图模型)时,这种方法也是有效的。

例如

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        /*
         Here I am passing these arguments to a viewmodel 
        */
        viewModel.prepareData(arguments) 
        
        --------
        --------
        --------

    }

谢谢。


0

自从 androidx.fragment 模块的v1.3.0版本(2021年2月)起,他们废弃了 onAttachFragment() 回调函数,取而代之的是使用 FragmentOnAttachListener,它将附加到您的 FragmentManager中。因此,在您的FragmentActivityonCreate方法中,您可以这样做:

getSupportFragmentManager().addFragmentOnAttachListener((fragmentManager, fragment) -> {
    if (fragment instanceof MyFragment) {
        Bundle args = getIntent().getExtras();
        fragment.setArguments(args);
    }
}

如果您在XML中使用了<fragment>,则此代码将在setContentView()之后触发,但如果您使用了<FragmentContainerView>,则稍后会发生。

请注意,我已经注意到配置更改可能会在super.onCreate内重新附加现有的Fragment,因此需要在此点之前添加侦听器。 (也就是说,这对于传递Bundle参数并不是必需的,否则应首先尝试使用onSaveInstanceState()。)


顺便提一下,如果需要,在“onAttach”中监听Fragment的视图生命周期也是添加观察者的好地方,这样一旦Fragment的视图被实例化,就可以在Activity中执行任何操作。例如,添加:

        LiveData<LifecycleOwner> liveData = fragment.getViewLifecycleOwnerLiveData();
        // Let this observer be constrained to the fragment's lifecycle.
        liveData.observe(fragment, new Observer<LifecycleOwner>() {
            @Override
            public void onChanged(LifecycleOwner lifecycleOwner) {
                // ...do work...
                // If you don't need to listen anymore, can clean ourselves up early.
                liveData.removeObserver(this);
            }
        });

或者作为观察片段视图生命周期的替代方法(并替换片段已弃用的onActivityCreated()),您可以从片段监听另一种方式,即从片段监听活动的生命周期!从片段的onAttach(context)回调中,添加自己的observer,该观察者实现onCreate(@NonNull LifecycleOwner owner)。参考:onActivityCreated已弃用,如何正确使用LifecycleObserver?


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