从DialogFragment回调片段

时间:2012-12-05 22:04:14

标签: android android-fragments callback android-dialogfragment

问题:如何创建从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在其生命周期中暂停和恢复,则无法保证听众会出现。片段中唯一的保证是通过setArguments和getArguments通过Bundle传入的。

如果活动应该是听众,有一种方法可以引用该活动:

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

但我不想让活动听取事件,我需要一个片段。实际上,它可以是任何实现OnClickListener的Java对象。

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

16 个答案:

答案 0 :(得分:178)

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

答案 1 :(得分:62)

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

  

“关键android:target_state:index 1”

的片段不再存在

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

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

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

通过使用接口,在onCreate的{​​{1}}方法中,您可以获得父片段:

DialogFragment

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

有关该问题的详细信息,请查看以下链接: https://code.google.com/p/android/issues/detail?id=54520

答案 2 :(得分:31)

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

您可以在显示之前在setTargetFragment上使用Dialog,在对话框中,您可以致电getTargetFragment以获取参考。

答案 3 :(得分:31)

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

  1. 使用callBackMethod(Object data)之类的方法创建DialogFragmentCallbackInterface之类的界面。您打算传递数据。
  2. 现在,您可以在片段MyFragment implements DialogFragmentCallbackInterface
  3. 中实施DialogFragment界面
  4. MyFragment创建时,将您的调用片段DialogFragment设置为创建myDialogFragment.setTargetFragment(this, 0)的目标片段,使用MyDialogFragment dialogFrag = new MyDialogFragment(); dialogFrag.setTargetFragment(this, 1); 检查setTargetFragment (Fragment fragment, int requestCode)

    DialogFragment
  5. 通过调用getTargetFragment()将目标片段对象放入DialogFragmentCallbackInterface并将其转换为DialogFragmentCallbackInterface callback = (DialogFragmentCallbackInterface) getTargetFragment(); callback.callBackMethod(Object data); 。现在您可以使用此界面将数据发送到您的片段。

    HighLevel.ps1
      LowLevelModule1.psm1
      LowLevelModule2.psm1
      ...
    

    这一切都完成了!只需确保您已在片段中实现此界面。

答案 4 :(得分:19)

Communicating with Other Fragments指南说碎片应通过相关活动进行沟通

  

通常你会想要一个片段与另一个片段进行通信   示例根据用户事件更改内容。所有   片段到片段的通信是通过相关联的   活动。两个碎片永远不应该直接沟通。

答案 5 :(得分: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){

    }
    ...
}

答案 6 :(得分:3)

Kotlin伙计们我们来了!

所以我们遇到的问题是,我们在该活动上创建了一个活动MainActivity,我们创建了一个片段FragmentA,现在我们想在{{1}顶部创建一个对话框片段}称为FragmentA。我们如何不经过FragmentB就将结果从FragmentB返回到FragmentA

注意:

  1. MainActivityFragmentA的子片段。为了管理在MainActivity中创建的片段,我们将使用childFragmentManager来做到这一点!
  2. FragmentAFragmentA的父片段,要从FragmentB内部访问FragmentA,我们将使用parenFragment

FragmentB内部,

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")
    }
}

就是这样。

答案 7 :(得分:2)

我遇到了类似的问题。我发现的解决方案是:

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

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

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

  4. 因此,它变成了两个步骤:DialogFragment - >活动然后活动 - >片段

答案 8 :(得分:1)

我从Fragment 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 {
                    }
                }
            }
        }
    }

答案 9 :(得分:1)

<强>更新

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

https://github.com/zeroarst/callbackfragment

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

旧答案:

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

然后你只需要扩展它并在你的片段中声明你的接口并给它注释,基本片段将完成其余的工作。注释还有一个参数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

答案 10 :(得分:1)

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

            }
        });
    }
}

答案 11 :(得分:1)

根据官方文档:

Fragment#setTargetFragment

  

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

Fragment#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");
        }
    }
    ...
}

答案 12 :(得分: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

答案 13 :(得分:0)

    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)

//我的fragment实现了CallbackDialogFragment,所以设置它作为显示方法

    **in dialog fragment**

    lateinit var callBackResult: CallbackDialogFragment


    fun display(fragmentManager: FragmentManager, callback: CallbackDialogFragment) {
        callBackResult = callback
        show(fragmentManager,"dialogTag")
    }

答案 14 :(得分:0)

更多改进的方法是仅使用 newInstanceinterface

这是一个需要 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");
    }
}
}

享受吧!

答案 15 :(得分:0)

建议在 official Android documentation 中使用 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();
    }
});