173

问题:如何创建从 DialogFragment 到另一个 Fragment 的回调。就我而言,所涉及的 Activity 应该完全不知道 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 中的唯一保证是那些通过 Bundle 通过 setArguments 和 getArguments 传入的保证。

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

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

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

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

4

17 回答 17

195

所涉及的活动完全不知道 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;
        }
    }
}

}

对话片段类:

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();
}
}
于 2012-12-05T22:46:53.573 回答
88

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

“关键 android:target_state: index 1 的片段不再存在”

它似乎Fragment#setTargetFragment()不是用于子片段和父片段之间的通信,而是用于兄弟片段之间的通信。

因此,替代方法是使用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

于 2015-12-14T14:02:02.130 回答
39

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

  1. DialogFragmentCallbackInterface使用类似的方法创建接口callBackMethod(Object data)。您会调用它来传递数据。
  2. 现在您可以DialogFragmentCallbackInterface在片段中实现接口,例如MyFragment implements DialogFragmentCallbackInterface
  3. DialogFragment创建时将您的调用片段设置MyFragment为创建DialogFragment使用myDialogFragment.setTargetFragment(this, 0)检查setTargetFragment (Fragment fragment, int requestCode)的目标片段

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

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

    这一切都完成了!只要确保你已经在你的片段中实现了这个接口。

于 2015-06-16T18:38:39.903 回答
36

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

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

于 2013-01-07T00:24:33.107 回答
19

Communicating with Other Fragments指南说 Fragments 应该通过相关的 Activity 进行通信。

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

于 2013-10-18T14:40:14.730 回答
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){

    }
    ...
}
于 2012-12-05T23:43:54.620 回答
8

根据官方文档:

片段#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");
        }
    }
    ...
}
于 2019-08-17T02:07:49.957 回答
6

推荐的方法是使用新的Fragment Result API

通过使用它,您不需要覆盖 onAttach(context)setTargetFragment()现在已弃用


1 - 在父片段上添加一个结果监听器onCreate

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    childFragmentManager.setFragmentResultListener("requestKey", this) { key, bundle ->
        val result = bundle.getString("bundleKey")
    }

}

2- 在片段上,设置结果(例如,在按钮单击侦听器上):

button.setOnClickListener {
    val result = "resultSample"

    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

有关文档的更多信息:https ://developer.android.com/guide/fragments/communicate#fragment-result

希望能帮助到你!

于 2021-10-18T20:21:37.813 回答
5

将侦听器设置为片段的正确方法是在附加时设置它。我遇到的问题是从未调用过 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() {

            }
        });
    }
}
于 2017-06-22T07:34:25.607 回答
5

更新:请注意,如果有人感兴趣,我可以分享使用视图模型的更简单方法。

Kotlin 伙计们,我们开始吧!

所以我们遇到的问题是我们创建了一个活动,MainActivity在那个活动上我们创建了一个片段,FragmentA现在我们想在FragmentA调用它的基础上创建一个对话框片段FragmentB。我们如何在不经过的情况下从FragmentBback to获得结果?FragmentAMainActivity

笔记:

  1. FragmentA是 的子片段MainActivity。为了管理在其中创建的片段,FragmentA我们将使用childFragmentManagerwhich 来执行此操作!
  2. FragmentA是 的父片段,要从内部FragmentB访问,我们将使用.FragmentAFragmentBparenFragment

话虽如此,在里面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)
}

而已。

于 2020-05-04T21:42:45.123 回答
2

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

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

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

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

因此,它变成了一个两步过程:DialogFragment -> Activity,然后是 Activity -> Fragment

于 2015-08-22T10:37:24.883 回答
1
    this is work for me
    i think you can set callback in display method in your fragment,
    

    **in my fragment**

    val myDialogFragment=MyDialogFragment()
    myDialogFragment.display(fragmentManager!!,this)

//我的片段实现了CallbackDialogFragment所以设置这个显示方法

    **in dialog fragment**

    lateinit var callBackResult: CallbackDialogFragment


    fun display(fragmentManager: FragmentManager, callback: CallbackDialogFragment) {
        callBackResult = callback
        show(fragmentManager,"dialogTag")
    }
于 2021-03-05T11:18:17.187 回答
1

更新:

我根据我的 gist 代码创建了一个库,它使用@CallbackFragmentand为您生成这些转换@Callback

https://github.com/zeroarst/callbackfragment

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

老答案:

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

然后你只需要扩展它并在你的片段中声明你的接口并给它注解,剩下的基本片段将完成。注解还有一个参数mandatory供你判断是否要强制fragment实现回调。

public class EchoFragment extends BaseCallbackFragment {

    private FragmentInteractionListener mListener;

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

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

于 2017-05-30T05:04:04.907 回答
1

我从 Fragment LiveWallFilterFragment(receiving fragment) 得到结果到 Fragment DashboardLiveWall(calling fragment) 像这样......

 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 {
                    }
                }
            }
        }
    }
于 2017-05-04T06:27:22.717 回答
0

更改进的方法是只使用newInstanceand interface

这是一个需要的片段DailogFragment

public class Fragment extends Fragment implements returnPinInterface {
....
....
public View onCreateView(@NotNull LayoutInflater inflater, ViewGroup 
container,Bundle savedInstanceState) {

// A simple call to show DialogFragment

   btnProceed.setOnClickListener(v -> {
        fragment = DailogFragment.newInstance(this);
        fragment.show(getChildFragmentManager(),null );
        fragment.setCancelable(false);
    });


   //Grab whatever user clicked/selected/ or typed
   @Override
   public void onPinReturn(String s) {
      Log.d("ReturnedPin", s);
   }
}

你的 DialogFragment 来了

public class PinDialogFragment extends DialogFragment {

 //Create a static variable to help you receive instance of fragment you 
 //passed
public static Fragment fragm;


  // Create new Instance and grab the object passed in Fragment up 
  //there
  public static PinDialogFragment newInstance(Fragment frag) {
    PinDialogFragment fragment = new PinDialogFragment();
    fragm = frag;
    return fragment;
  }

public View onCreateView(LayoutInflater inflater, ViewGroup container,
                         Bundle savedInstanceState) {
   View v = inflater.inflate(R.layout.fragment_pin, container, false);

  //
   btn.setOnClickListener(btn ->                         
   listener.onReturnPin("1234"));
   
   return v;
}


//Use the Fragm to instantiate your Interface

@Override
public void onAttach(Context context) {
    super.onAttach(context);

    if (fragm instanceof ReturnPinInterface) {
        listener = (ReturnPinInterface) fragm;
    } else {
        throw new RuntimeException("you must implement ReturnPinInterface");
    }
}
}

享受!

于 2021-04-27T21:22:45.673 回答
0

我用 RxAndroid 以一种优雅的方式解决了这个问题。在 DialogFragment 的构造函数中接收一个观察者,并在调用回调时订阅 observable 并推送该值。然后,在您的 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

于 2017-06-20T05:05:40.800 回答
0

官方 Android 文档onAttach中建议使用。因此,我们可以利用该方法。

确保您的父片段实现了一个监听器,例如OnPopupButtonClickListener

public interface OnPopupButtonClickListener {
   void onPositiveButtonClicked();
   void onNegativeButtonClicked();
}

在您的父片段中显示您的DialogFragment实例,使用getChildFragmentManager()

PopupDialogFragment dialogFragment = new PopupDialogFragment();
dialogFragment.show(getChildFragmentManager(), "PopupDialogFragment");

在扩展DialogFragment实例的对话框类中添加此方法:(请注意,我们正在检索我们的父片段,通过getParentFragment()它实现我们的自定义侦听器接口OnPopupButtonClickListener

@Override
public void onAttach(@NonNull @NotNull Context context) {
   super.onAttach(context);
   // Verify that the host activity implements the callback interface
   try {
      listener = (OnPopupButtonClickListener) getParentFragment();
   } catch (ClassCastException e) {
      // The activity doesn't implement the interface, throw exception
      throw new ClassCastException(getActivity().toString()
                    + " must implement OnPopupButtonClickListener");
   }
}

在您的对话框中,您可以在需要时使用您的侦听器DialogFragment,例如:

Button positiveButton = view.findViewById(R.id.positiveButton);
positiveButton.setOnClickListener(v -> {
    if (listener != null) {
       listener.onPositiveButtonClicked();
       getDialog().dismiss();
    }
});
于 2021-07-29T08:45:36.440 回答