问题:如何创建从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按钮。如何将这些按钮发送回创建它的片段?
答案 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)
我按照这个简单的步骤来做这些事情。
callBackMethod(Object data)
之类的方法创建DialogFragmentCallbackInterface
之类的界面。您打算传递数据。MyFragment implements DialogFragmentCallbackInterface
DialogFragment
界面
在MyFragment
创建时,将您的调用片段DialogFragment
设置为创建myDialogFragment.setTargetFragment(this, 0)
的目标片段,使用MyDialogFragment dialogFrag = new MyDialogFragment();
dialogFrag.setTargetFragment(this, 1);
检查setTargetFragment (Fragment fragment, int requestCode)
DialogFragment
通过调用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
?
注意:
MainActivity
是FragmentA
的子片段。为了管理在MainActivity
中创建的片段,我们将使用childFragmentManager
来做到这一点!FragmentA
是FragmentA
的父片段,要从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)
我遇到了类似的问题。我发现的解决方案是:
在您的DialogFragment中声明一个接口,就像James McCracken上面所解释的那样。
在您的活动中实现界面(不是片段!这不是一个好习惯)。
从您活动中的回调方法中,在您的片段中调用所需的公共函数,该函数执行您想要执行的工作。
因此,它变成了两个步骤: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)
根据官方文档:
此片段的可选目标。例如,如果此片段是由另一个片段开始的,并且在完成后想要将结果返回给第一个片段,则可以使用此方法。此处设置的目标是通过FragmentManager#putFragment跨实例保留的。
返回由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)
更多改进的方法是仅使用 newInstance
和 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");
}
}
}
享受吧!
答案 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();
}
});