问题:如何创建从 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 的具体示例。它有是/否按钮。如何将这些按钮按下发送回创建它的片段?
OnClickListener listener = (OnClickListener) getParentFragment();
,而你的主 Fragment 像你最初那样实现接口。
所涉及的活动完全不知道 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();
}
}
TargetFragment 解决方案似乎不是对话框片段的最佳选择,因为它可能会在应用程序被销毁并重新创建后创建 IllegalStateException
。在这种情况下,FragmentManager
找不到目标片段,您将收到带有如下消息的 IllegalStateException
:
“关键 android:target_state: index 1 的片段不再存在”
似乎 Fragment#setTargetFragment()
不是用于子片段和父片段之间的通信,而是用于兄弟片段之间的通信。
因此,另一种方法是使用父片段的 ChildFragmentManager
创建这样的对话框片段,而不是使用活动 FragmentManager
:
dialogFragment.show(ParentFragment.this.getChildFragmentManager(), "dialog_fragment");
通过使用接口,在 DialogFragment
的 onCreate
方法中,您可以获得父片段:
@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
我按照这个简单的步骤来做这些事情。
使用诸如 callBackMethod(Object data) 之类的方法创建诸如 DialogFragmentCallbackInterface 之类的接口。您会调用它来传递数据。现在您可以在片段中实现 DialogFragmentCallbackInterface 接口,例如 MyFragment 实现 DialogFragmentCallbackInterface 在创建 DialogFragment 时将调用片段 MyFragment 设置为创建 DialogFragment 的目标片段使用 myDialogFragment.setTargetFragment(this, 0) check setTargetFragment (Fragment fragment, int requestCode) MyDialogFragment dialogFrag =新的 MyDialogFragment(); dialogFrag.setTargetFragment(this, 1);通过调用 getTargetFragment() 将目标片段对象放入 DialogFragment 并将其转换为 DialogFragmentCallbackInterface。现在您可以使用此接口将数据发送到片段。 DialogFragmentCallbackInterface 回调 = (DialogFragmentCallbackInterface) getTargetFragment(); callback.callBackMethod(对象数据);这一切都完成了!只要确保你已经在你的片段中实现了这个接口。
onActivityResult(request code, resultcode, intent)
将结果返回给 fragment1。从 fragment1,setTargetFragment()
和从 fragment2,使用 getTargetFragment()
。当使用父/子片段到片段或活动到片段时,您可以使用侦听器或回调,因为在子片段中没有任何泄漏父级的危险。
setTargetFragment
来设置回调。像魅力一样工作!
也许有点晚了,但可能会像我一样帮助其他有同样问题的人。
您可以在显示之前在 Dialog
上使用 setTargetFragment
,在对话框中您可以调用 getTargetFragment
来获取参考。
Communicating with Other Fragments 指南说片段应该通过关联的 Activity 进行通信。
通常,您会希望一个 Fragment 与另一个 Fragment 进行通信,例如根据用户事件更改内容。所有 Fragment 到 Fragment 的通信都是通过关联的 Activity 完成的。两个 Fragment 永远不应该直接通信。
推荐的方法是使用新的 Fragment Result API。
通过使用它,您无需覆盖 onAttach(context) 或 setTargetFragment(),后者现已弃用。
1 - 在 parent Fragment 的 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
希望能帮助到你!
您应该在片段类中定义一个 interface
并在其父 Activity 中实现该接口。此处概述了详细信息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){
}
...
}
FragmentA
,他假设一个活动是OnArticleSelectedListener
,而不是启动他的片段。
MyFragment
内处理所有内容,您可能需要切换到使用常规 AlertDialog
根据官方文档:
此片段的可选目标。例如,如果此片段正在由另一个片段启动,并且完成后希望将结果返回给第一个片段,则可以使用此方法。此处设置的目标通过 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");
}
}
...
}
将侦听器设置为片段的正确方法是在附加时设置它。我遇到的问题是从未调用过 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() {
}
});
}
}
更新:请注意,如果有人感兴趣,我可以分享使用视图模型的更简单方法。
Kotlin 伙计们,我们开始吧!
所以我们遇到的问题是我们创建了一个活动 MainActivity
,在该活动上我们创建了一个片段 FragmentA
,现在我们想在 FragmentA
之上创建一个对话框片段,将其称为 FragmentB
。我们如何在不经过 MainActivity
的情况下将 FragmentB
的结果返回到 FragmentA
?
笔记:
FragmentA 是 MainActivity 的子 Fragment。为了管理在 FragmentA 中创建的片段,我们将使用 childFragmentManager 来执行此操作! FragmentA 是 FragmentB 的父 Fragment,要从 FragmentB 内部访问 FragmentA,我们将使用 parenFragment。
话虽如此,在 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)
}
而已。
我面临着类似的问题。我发现的解决方案是:
就像 James McCracken 上面解释的那样,在 DialogFragment 中声明一个接口。在您的活动中实现接口(不是片段!这不是一个好习惯)。从您活动中的回调方法中,调用片段中所需的公共函数,该函数执行您想要执行的工作。
因此,它变成了一个两步过程:DialogFragment -> Activity,然后是 Activity -> Fragment
我从 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 {
}
}
}
}
}
更新:
我根据我的 gist 代码创建了一个库,它使用 @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
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")
}
如何使用 setFragmentResultListener
的完整示例:
父片段 MainFragment.kt
:
import android.os.Bundle
import android.view.View
import android.widget.Button
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.setFragmentResultListener
class MainFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val showDialogButton = view.findViewById<Button>(R.id.dialog_button)
showDialogButton.setOnClickListener {
showMyDialog()
}
}
private fun showMyDialog() {
MyDialogFragment.showOn(this) { bundle ->
/*here handle bundle result*/
}
}
}
你的对话:
import android.os.Bundle
import android.view.View
import android.widget.Button
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.setFragmentResult
import androidx.fragment.app.setFragmentResultListener
class MyDialogFragment : DialogFragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val submitButton = view.findViewById<Button>(R.id.submitButton)
submitButton.setOnClickListener {
parentFragment?.setFragmentResult(KEY_CALLBACK_BUNDLE, buildResultBundle())
}
}
private fun buildResultBundle(): Bundle {
val bundle = Bundle()
/*here build your result bundle for parent fragment*/
return bundle
}
companion object {
const val TAG: String = "MyDialogFragment"
private const val KEY_CALLBACK_BUNDLE: String = "KEY_CALLBACK_BUNDLE"
fun showOn(fragment: Fragment, callback: (Bundle) -> Unit) {
val dialog = MyDialogFragment()
fragment.setFragmentResultListener(KEY_CALLBACK_BUNDLE) { requestKey: String, bundle: Bundle ->
if (requestKey == KEY_CALLBACK_BUNDLE) {
callback(bundle)
}
}
dialog.show(fragment.childFragmentManager, TAG)
}
}
}
我用 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
更改进的方法是只使用 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");
}
}
}
享受!
在 official Android documentation 中建议使用 onAttach
。因此,我们可以利用该方法。
确保您的父片段实现了一个侦听器,例如 OnPopupButtonClickListener
:
public interface OnPopupButtonClickListener {
void onPositiveButtonClicked();
void onNegativeButtonClicked();
}
在您的父片段中使用 getChildFragmentManager()
显示您的 DialogFragment
实例:
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();
}
});
setTargetFragment
和getTargetFragment
。onActivityResult
的使用有点不清楚。在 Fragment 调用者中声明您自己的特定方法并使用它可能会更好,而不是重新利用 onActivityResult。但那时它的所有语义。