从DialogFragment回调到片段


159

问题:如何创建从DialogFragment到另一个Fragment的回调。就我而言,所涉及的活动应该完全不知道DialogFragment。

认为我有

public class MyFragment extends Fragment implements OnClickListener

然后在某个时候我可以

DialogFragment dialogFrag = MyDialogFragment.newInstance(this);
dialogFrag.show(getFragmentManager, null);

MyDialogFragment的样子

protected OnClickListener listener;
public static DialogFragment newInstance(OnClickListener listener) {
    DialogFragment fragment = new DialogFragment();
    fragment.listener = listener;
    return fragment;
}

但是,如果DialogFragment暂停并在其生命周期中恢复,则无法保证侦听器会存在。Fragment中唯一的保证是通过setArguments和getArguments通过Bundle传递的保证。

有一种方法可以引用活动(如果它应该是侦听器):

public Dialog onCreateDialog(Bundle bundle) {
    OnClickListener listener = (OnClickListener) getActivity();
    ....
    return new AlertDialog.Builder(getActivity())
        ........
        .setAdapter(adapter, listener)
        .create();
}

但是我不希望Activity监听事件,我需要一个Fragment。实际上,它可以是实现OnClickListener的任何Java对象。

考虑一个片段的具体示例,该片段通过DialogFragment呈现AlertDialog。它具有是/否按钮。如何将这些按钮按下操作发送回创建它的片段?


您提到“但是,如果DialogFragment暂停并在其生命周期中恢复,则无法保证侦听器会存在。” 我以为Fragment状态在onDestroy()期间被破坏了?您一定是正确的,但是现在我有点困惑如何使用Fragment状态。我如何重现您提到的问题,而听众不在附近?
肖恩

我不明白为什么不能简单地OnClickListener listener = (OnClickListener) getParentFragment();在DialogFragment中使用,而您的主要Fragment却像最初那样实现了接口。
kiruwka

这里是一个答案,一个不相关的问题,但它不告诉你这是如何在一个干净的方式完成stackoverflow.com/questions/28620026/...
user2288580

Answers:


190

涉及的活动完全不知道DialogFragment。

片段类:

public class MyFragment extends Fragment {
int mStackLevel = 0;
public static final int DIALOG_FRAGMENT = 1;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    if (savedInstanceState != null) {
        mStackLevel = savedInstanceState.getInt("level");
    }
}

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    outState.putInt("level", mStackLevel);
}

void showDialog(int type) {

    mStackLevel++;

    FragmentTransaction ft = getActivity().getFragmentManager().beginTransaction();
    Fragment prev = getActivity().getFragmentManager().findFragmentByTag("dialog");
    if (prev != null) {
        ft.remove(prev);
    }
    ft.addToBackStack(null);

    switch (type) {

        case DIALOG_FRAGMENT:

            DialogFragment dialogFrag = MyDialogFragment.newInstance(123);
            dialogFrag.setTargetFragment(this, DIALOG_FRAGMENT);
            dialogFrag.show(getFragmentManager().beginTransaction(), "dialog");

            break;
    }
}

@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
        switch(requestCode) {
            case DIALOG_FRAGMENT:

                if (resultCode == Activity.RESULT_OK) {
                    // After Ok code.
                } else if (resultCode == Activity.RESULT_CANCELED){
                    // After Cancel code.
                }

                break;
        }
    }
}

}

DialogFragment类:

public class MyDialogFragment extends DialogFragment {

public static MyDialogFragment newInstance(int num){

    MyDialogFragment dialogFragment = new MyDialogFragment();
    Bundle bundle = new Bundle();
    bundle.putInt("num", num);
    dialogFragment.setArguments(bundle);

    return dialogFragment;

}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {

    return new AlertDialog.Builder(getActivity())
            .setTitle(R.string.ERROR)
            .setIcon(android.R.drawable.ic_dialog_alert)
            .setPositiveButton(R.string.ok_button,
                    new DialogInterface.OnClickListener() {
                        public void onClick(DialogInterface dialog, int whichButton) {
                            getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, getActivity().getIntent());
                        }
                    }
            )
            .setNegativeButton(R.string.cancel_button, new DialogInterface.OnClickListener() {
                public void onClick(DialogInterface dialog, int whichButton) {
                    getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_CANCELED, getActivity().getIntent());
                }
            })
            .create();
}
}

100
我认为这里的关键是setTargetFragmentgetTargetFragment。的使用onActivityResult还不清楚。最好在Fragment调用程序中声明自己的特定方法,然后使用它,而不是重新使用onActivityResult。但是这时的所有语义。
eternalmatt

2
堆栈级别的变量不使用?
显示名称

6
这样可以在配置变更中幸免吗?
Maxrunner

3
用这个。注意:堆栈级别对于生存旋转或睡眠不是必需的。我的片段代替了onActivityResult,而是实现了DialogResultHandler#handleDialogResult(我创建的接口)。@myCode,对于显示一个对话框选择的值将被添加到Intent,然后在onActivityResult内部进行读取非常有帮助。初学者不清楚意图。
克里斯·贝蒂

7
@eternalmatt,您的反对是完全合理的,但是我认为onActivityResult()的价值在于它可以保证存在于任何Fragment上,因此任何Fragment都可以用作父对象。如果创建自己的接口并让父Fragment实现它,则该子项只能与实现该接口的父项一起使用。如果您以后更广泛地使用孩子,那么将孩子与该界面耦合可能会再次困扰您。使用“内置” onActivityResult()接口不需要额外的耦合,因此它使您更具灵活性。
Dalbergia 2015年

78

TargetFragment解决方案似乎不是对话框片段的最佳选择,因为它可能IllegalStateException在应用程序销毁并重新创建后创建。在这种情况下FragmentManager,找不到目标片段,您将收到一条IllegalStateException带有以下消息的消息:

“片段不再存在于键android:target_state:索引1”

看来这Fragment#setTargetFragment()并不意味着要在子代与父代Fragment之间进行通信,而是为了同级-Fragments之间进行通信。

因此,另一种方法是通过使用ChildFragmentManager父片段的而不是活动来创建这样的对话框片段FragmentManager

dialogFragment.show(ParentFragment.this.getChildFragmentManager(), "dialog_fragment");

并通过使用接口,在中的onCreate方法DialogFragment可以获取父片段:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    try {
        callback = (Callback) getParentFragment();
    } catch (ClassCastException e) {
        throw new ClassCastException("Calling fragment must implement Callback interface");
    }
}

这些步骤之后,剩下的就是调用您的回调方法。

有关此问题的更多信息,您可以查看以下链接:https : //code.google.com/p/android/issues/detail?id=54520


2
这也适用于介绍API 23.添加onAttach(上下文的背景下)
圣Teclado

1
这应该是公认的答案。当前接受的答案是越野车,并且片段不打算这样使用。
NecipAllef

3
@AhmadFadli这里的问题是为片段之间的通信获取正确的上下文(父级)。如果您将对话框片段用作活动的子元素,那么应该不会有任何混淆。Activity的FragmentManager和获取回调的getActivity()足够了。
Oguz Ozcan

1
这应该是公认的答案。清楚地解释和详细说明了它,而不仅仅是将代码扔给人们。
文斯

1
@lukecross ParentFragment是创建DialogFragment的片段(调用show()的片段),但看起来childFragmentManager不能在重新配置/屏幕旋转后幸免于难……
iwat0qs

34

我按照这些简单的步骤来做这些事情。

  1. 创建接口,例如DialogFragmentCallbackInterface使用callBackMethod(Object data)。您要调用以传递数据的对象。
  2. 现在您可以DialogFragmentCallbackInterface在片段中实现接口,例如MyFragment implements DialogFragmentCallbackInterface
  3. DialogFragment创建时,将调用的片段设置为创建的MyFragment目标片段,请DialogFragment使用myDialogFragment.setTargetFragment(this, 0)check setTargetFragment(片段片段,int requestCode)

    MyDialogFragment dialogFrag = new MyDialogFragment();
    dialogFrag.setTargetFragment(this, 1); 
  4. DialogFragment通过调用将目标片段对象放入您的对象getTargetFragment()并将其DialogFragmentCallbackInterface强制转换为。现在,您可以使用此接口将数据发送到片段。

    DialogFragmentCallbackInterface callback = 
               (DialogFragmentCallbackInterface) getTargetFragment();
    callback.callBackMethod(Object data);

    全部完成!只需确保已在片段中实现此接口即可。


4
这应该是最好的答案。好答案。
Sajedul Karim博士

另外,请确保对源和目标片段使用相同的片段管理器,否则getTargetFragment将不起作用。因此,如果您正在使用childFragmentManager,则由于源片段不是由子片段管理器提交的,因此它将无法工作。最好将这两个片段视为同级片段,而不是父/子片段。
Thupten

坦白地说,在两个同级片段之间进行通信时最好只使用目标片段模式。通过没有侦听器,可以避免意外地将fragment1泄漏到fragment2中。使用目标片段时,请勿使用侦听器/回调。仅用于onActivityResult(request code, resultcode, intent)将结果返回给fragment1。在fragment1 setTargetFragment()和fragment2中,使用getTargetFragment()。使用父/子片段进行分段或活动进行分段时,可以使用侦听器或回调,因为在子片段中不存在泄漏父项的危险。
Thupten

@Thupten当您说“泄漏”时,是指内存泄漏还是“泄漏实现细节”?我认为Vijay的答案不会比使用onActivityResulty作为返​​回值泄漏内存更多。两种模式都将继续引用目标片段。如果您是说泄漏实现细节,那么我认为他的模式甚至比onActivityResult更好。回调方法是显式的(如果命名正确)。如果您一切都好,并且已取消,则第一个片段必须解释这些含义。
tir38

34

也许有点晚,但可能会像我一样帮助其他人解决相同的问题。

您可以在显示前使用setTargetFragmenton Dialog,在对话框中可以调用getTargetFragment获取参考。


这里是一个回答另一个问题,但它也适用于你的问题,是一个干净的解决方案:stackoverflow.com/questions/28620026/...
user2288580

IllegalStateException对我来说
路过

19

与其他碎片通信指南说的片段应该通过相关的活动交流

通常,您会希望一个Fragment与另一个Fragment进行通信,例如,根据用户事件更改内容。所有片段到片段的通信都是通过关联的活动完成的。两个片段永远不要直接通信。


1
内部片段如何处理,即另一个片段中的片段应如何与主机片段进行通信
Ravi

@Ravi:每个片段都应该通过调用getActivity()与所有片段共有的活动进行通信。
爱德华·布雷

1
@Chris:如果片段需要持续的通信,请为每个要实现的片段定义一个接口。然后,活动的工作仅限于为片段提供指向其对应片段的接口指针。之后,片段可以通过接口安全地“直接”通信。
爱德华·布雷

3
我认为,随着片段用途的扩展,不使用直接片段通讯的原始想法崩溃了。例如,在导航抽屉中,活动的每个直接子片段大致充当活动。因此,使诸如对话片段之类的片段通过活动进行通信会损害IMO的可读性/灵活性。实际上,似乎没有什么好办法可以封装dialogfragment以使其以可重用的方式与活动和片段一起使用。
萨姆

16
我知道这很古老,但是万一其他人来了,当一个片段“拥有”用于确定DialogFragment的创建和管理的逻辑时,我觉得该文档中讨论的情形不适用。当活动甚至不确定为什么要创建对话框或在什么条件下应该取消对话框时,创建从片段到活动的一连串连接有点奇怪。除此之外,DialogFragment非常简单,仅用于通知用户并可能获得响应。
克里斯(Chris

12

您应该interface在片段类中定义一个,并在其父活动中实现该接口。此处概述了详细信息,网址为http://developer.android.com/guide/components/fragments.html#EventCallbacks。该代码类似于:

分段:

public static class FragmentA extends DialogFragment {

    OnArticleSelectedListener mListener;

    // Container Activity must implement this interface
    public interface OnArticleSelectedListener {
        public void onArticleSelected(Uri articleUri);
    }

    @Override
    public void onAttach(Activity activity) {
        super.onAttach(activity);
        try {
            mListener = (OnArticleSelectedListener) activity;
        } catch (ClassCastException e) {
            throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener");
        }
    }
}

活动:

public class MyActivity extends Activity implements OnArticleSelectedListener{

    ...
    @Override
    public void onArticleSelected(Uri articleUri){

    }
    ...
}

1
我认为您过快浏览了文档。这两个代码段都是,FragmentA并且他假设一个活动是OnArticleSelectedListener,而不是开始他的Fragment。
eternalmatt 2012年

2
我会考虑您要尝试的不良做法。Android准则建议所有片段到片段的通信都通过活动进行(根据developer.android.com/training/basics/fragments/…)。如果你真的想这一切处理中MyFragment你可能要切换到使用普通AlertDialog
詹姆斯·麦克拉肯

1
我认为让片段直接对话的问题在于,在某些布局中,并非所有片段都可能被加载,并且如示例中所示,可能有必要切入片段。在谈论从片段启动对话框片段时,我认为这种担心是没有道理的。

我已经为自己的活动实施了此功能。问题:是否可以扩展此解决方案,以便片段可以实例化此对话框?
比尔·莫特

1
从体系结构的角度来看,这是一个好习惯,因此应该被接受。使用onActivityResult会导致意大利面体系结构
Bruno Carrier

3

设置片段的侦听器的正确方法是在连接片段时对其进行设置。我遇到的问题是从未调用过onAttachFragment()。经过一番调查,我意识到我一直在使用getFragmentManager而不是getChildFragmentManager

这是我的方法:

MyDialogFragment dialogFragment = MyDialogFragment.newInstance("title", "body");
dialogFragment.show(getChildFragmentManager(), "SOME_DIALOG");

将其附加在onAttachFragment中:

@Override
public void onAttachFragment(Fragment childFragment) {
    super.onAttachFragment(childFragment);

    if (childFragment instanceof MyDialogFragment) {
        MyDialogFragment dialog = (MyDialogFragment) childFragment;
        dialog.setListener(new MyDialogFragment.Listener() {
            @Override
            public void buttonClicked() {

            }
        });
    }
}

3

根据官方文件:

片段#setTargetFragment

此片段的可选目标。例如,如果此片段是由另一个片段开始的,并且在完成后希望将结果返回给第一个片段,则可以使用此方法。此处设置的目标通过FragmentManager#putFragment跨实例保留。

片段#getTargetFragment

返回由setTargetFragment(Fragment,int)设置的目标片段。

因此,您可以执行以下操作:

// In your fragment

public class MyFragment extends Fragment implements OnClickListener {
    private void showDialog() {
        DialogFragment dialogFrag = MyDialogFragment.newInstance(this);
        // Add this
        dialogFrag.setTargetFragment(this, 0);
        dialogFrag.show(getFragmentManager, null);
    }
    ...
}

// then

public class MyialogFragment extends DialogFragment {
    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        // Then get it
        Fragment fragment = getTargetFragment();
        if (fragment instanceof OnClickListener) {
            listener = (OnClickListener) fragment;
        } else {
            throw new RuntimeException("you must implement OnClickListener");
        }
    }
    ...
}

你能解释一下吗?
Yilmaz

在这种情况下,我们需要将“ MyFragment”引用传递给“ MyialogFragment”,而“ Fragment”提供了执行此操作的方法。我添加了正式文件的描述,应该比我说得更清楚。
SUPERYAO

2

我面临着类似的问题。我发现的解决方案是:

  1. 就像James McCracken前面解释的那样,在DialogFragment中声明一个接口。

  2. 在您的活动中实现接口(不是片段!这不是一个好习惯)。

  3. 从活动中的回调方法中,在片段中调用所需的公共函数,该公共函数完成了您想要做的工作。

因此,它分为两个步骤:DialogFragment-> Activity,然后是Activity-> Fragment


1

我正在从片段LiveWallFilterFragment(接收片段)到Fragment DashboardLiveWall(调用片段)的结果...

 LiveWallFilterFragment filterFragment = LiveWallFilterFragment.newInstance(DashboardLiveWall.this ,"");

 getActivity().getSupportFragmentManager().beginTransaction(). 
 add(R.id.frame_container, filterFragment).addToBackStack("").commit();

哪里

public static LiveWallFilterFragment newInstance(Fragment targetFragment,String anyDummyData) {
        LiveWallFilterFragment fragment = new LiveWallFilterFragment();
        Bundle args = new Bundle();
        args.putString("dummyKey",anyDummyData);
        fragment.setArguments(args);

        if(targetFragment != null)
            fragment.setTargetFragment(targetFragment, KeyConst.LIVE_WALL_FILTER_RESULT);
        return fragment;
    }

setResult返回调用片段,如

private void setResult(boolean flag) {
        if (getTargetFragment() != null) {
            Bundle bundle = new Bundle();
            bundle.putBoolean("isWorkDone", flag);
            Intent mIntent = new Intent();
            mIntent.putExtras(bundle);
            getTargetFragment().onActivityResult(getTargetRequestCode(),
                    Activity.RESULT_OK, mIntent);
        }
    }

onActivityResult

@Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);

        if (resultCode == Activity.RESULT_OK) {
            if (requestCode == KeyConst.LIVE_WALL_FILTER_RESULT) {

                Bundle bundle = data.getExtras();
                if (bundle != null) {

                    boolean isReset = bundle.getBoolean("isWorkDone");
                    if (isReset) {

                    } else {
                    }
                }
            }
        }
    }

1

更新:

我根据要点代码创建了一个库,该库通过使用@CallbackFragment和来为您生成这些转换@Callback

https://github.com/zeroarst/callbackfragment

该示例为您提供了从一个片段向另一个片段发送回调的示例。

旧答案:

我做了BaseCallbackFragment和注释@FragmentCallback。当前扩展Fragment,您可以将其更改为DialogFragment并且可以使用。它按以下顺序检查实现:getTargetFragment()> getParentFragment()>上下文(活动)。

然后,您只需要扩展它并在您的片段中声明您的接口并为其添加注释,然后基础片段将完成其余工作。批注还具有一个参数,mandatory供您确定是否要强制片段实现回调。

public class EchoFragment extends BaseCallbackFragment {

    private FragmentInteractionListener mListener;

    @FragmentCallback
    public interface FragmentInteractionListener {
        void onEcho(EchoFragment fragment, String echo);
    }
}

https://gist.github.com/zeroarst/3b3f32092d58698a4568cdb0919c9a93


1

Kotlin伙计们我们来了!

因此,我们面临的问题是我们创建了一个活动,MainActivity在该活动上我们创建了一个片段,FragmentA现在我们想在FragmentA调用它之上创建一个对话框片段FragmentB。我们如何得到的结果FragmentBFragmentA不用经过MainActivity

注意:

  1. FragmentA是的子片段MainActivity。要管理在其中创建的片段,FragmentA我们将使用childFragmentManager该功能!
  2. FragmentA是的父片段FragmentB,我们将使用FragmentA从内部访问。FragmentBparenFragment

话虽如此,在里面FragmentA

class FragmentA : Fragment(), UpdateNameListener {
    override fun onSave(name: String) {
        toast("Running save with $name")
    }

    // call this function somewhere in a clickListener perhaps
    private fun startUpdateNameDialog() {
        FragmentB().show(childFragmentManager, "started name dialog")
    }
}

这是对话框片段FragmentB

class FragmentB : DialogFragment() {

    private lateinit var listener: UpdateNameListener

    override fun onAttach(context: Context) {
        super.onAttach(context)
        try {
            listener = parentFragment as UpdateNameListener
        } catch (e: ClassCastException) {
            throw ClassCastException("$context must implement UpdateNameListener")
        }
    }

    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
        return activity?.let {
            val builder = AlertDialog.Builder(it)
            val binding = UpdateNameDialogFragmentBinding.inflate(LayoutInflater.from(context))
            binding.btnSave.setOnClickListener {
                val name = binding.name.text.toString()
                listener.onSave(name)
                dismiss()
            }
            builder.setView(binding.root)
            return builder.create()
        } ?: throw IllegalStateException("Activity can not be null")
    }
}

这是链接两者的接口。

interface UpdateNameListener {
    fun onSave(name: String)
}

而已。


1
我遵循了这个文档:developer.android.com/guide/topics/ui/dialogs,它没有用。非常感谢。我希望这个父母片段的东西每次都能像预期的那样工作:)
UmutTekin

1
不要忘记在onDetach内将监听器设置为null :)
BekaBot

@BekaBot感谢您的评论。我已经做过一些研究,似乎没有必要关闭听众。stackoverflow.com/a/37031951/10030693
Ssenyonjo

0

我用RxAndroid很好地解决了这个问题。在DialogFragment的构造函数中接收观察者,并要求观察者可观察并在调用回调时推入该值。然后,在Fragment中创建Observer的内部类,创建一个实例并将其传递给DialogFragment的构造函数。我在观察器中使用了WeakReference以避免内存泄漏。这是代码:

BaseDialogFragment.java

import java.lang.ref.WeakReference;

import io.reactivex.Observer;

public class BaseDialogFragment<O> extends DialogFragment {

    protected WeakReference<Observer<O>> observerRef;

    protected BaseDialogFragment(Observer<O> observer) {
        this.observerRef = new WeakReference<>(observer);
   }

    protected Observer<O> getObserver() {
    return observerRef.get();
    }
}

DatePickerFragment.java

public class DatePickerFragment extends BaseDialogFragment<Integer>
    implements DatePickerDialog.OnDateSetListener {


public DatePickerFragment(Observer<Integer> observer) {
    super(observer);
}

@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
    // Use the current date as the default date in the picker
    final Calendar c = Calendar.getInstance();
    int year = c.get(Calendar.YEAR);
    int month = c.get(Calendar.MONTH);
    int day = c.get(Calendar.DAY_OF_MONTH);

    // Create a new instance of DatePickerDialog and return it
    return new DatePickerDialog(getActivity(), this, year, month, day);
}

@Override
public void onDateSet(DatePicker view, int year, int month, int dayOfMonth) {
        if (getObserver() != null) {
            Observable.just(month).subscribe(getObserver());
        }
    }
}

MyFragment.java

//Show the dialog fragment when the button is clicked
@OnClick(R.id.btn_date)
void onDateClick() {
    DialogFragment newFragment = new DatePickerFragment(new OnDateSelectedObserver());
    newFragment.show(getFragmentManager(), "datePicker");
}
 //Observer inner class
 private class OnDateSelectedObserver implements Observer<Integer> {

    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(Integer integer) {
       //Here you invoke the logic

    }

    @Override
    public void onError(Throwable e) {

    }

    @Override
    public void onComplete() {

    }
}

您可以在此处查看源代码:https : //github.com/andresuarezz26/carpoolingapp


1
关于Android的有趣的事情是有一个叫做生命周期的东西。基本片段或对话框片段需要能够在生命周期事件中保留状态(及其连接)。回调或观察者无法序列化,因此这里存在相同的问题。
GDanger
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.