DialogFragment and onDismiss

78

我正在使用一个 DialogFragment,从 Activity 中以以下方式进行显示:

DialogFragmentImage dialog = DialogFragmentImage.newInstance(createBitmap());
dialog.onDismiss(dialog);.onDismiss(this);          
dialog.show(getFragmentManager(), "DialogFragmentImage");

我想要检查DialogFragment何时被取消(例如当返回按钮被按下时),但是在我的Activity中。我该怎么做?我如何“告诉”我的activity DialogFragment已经被取消?


你可以在关闭对话框之前使用布尔值,并使用该布尔值通知活动。 - Santosh Kathait
好的,我该如何通知这个活动? - deimos1988
最初将布尔值设为false,而在关闭对话框之前将布尔值设为true,并使用布尔值通知活动以检查其是否为true或false。 - Santosh Kathait
1
我知道已经有一段时间了,但是你是否忘记接受答案了? - Yaroslav Mytkalyk
12个回答

116

让你的 Activity 实现 OnDismissListener

public final class YourActivity extends Activity implements DialogInterface.OnDismissListener {

    @Override
    public void onDismiss(final DialogInterface dialog) {
        //Fragment dialog had been dismissed
    }

}

DialogFragment已经实现了OnDismissListener接口,只需要重写该方法并调用Activity即可。

public final class DialogFragmentImage extends DialogFragment {

    ///blah blah

    @Override
    public void onDismiss(final DialogInterface dialog) {
        super.onDismiss(dialog);
        final Activity activity = getActivity();
        if (activity instanceof DialogInterface.OnDismissListener) {
            ((DialogInterface.OnDismissListener) activity).onDismiss(dialog);
        }
    }

}

如果您在片段中使用childFragment管理器(API>=17)开始对话框,则可以使用getParentFragment与父片段上的onDismissListener进行通信。

public final class DialogFragmentImage extends DialogFragment {

    ///blah blah

    @Override
    public void onDismiss(final DialogInterface dialog) {
        super.onDismiss(dialog);
        Fragment parentFragment = getParentFragment();
        if (parentFragment instanceof DialogInterface.OnDismissListener) {
            ((DialogInterface.OnDismissListener) parentFragment).onDismiss(dialog);
        } 
    }

}

1
@ashishduh,OP正在使用DialogFragment,而不是dialog。 - Blackbelt
9
你无法为由 DialogFragment 拥有的对话框设置 dismiss 监听器,Doctoror Drive 提供的解决方案是正确的。只是不要忘记调用 super.onDismiss(dialog),否则会出现问题。 - nitzanj
1
@ashishduh 我建议你删除你的评论。你让我查阅了文档。 - Hades
1
@lukecross,我的以前的用户名是Doctoror Drive。 - Yaroslav Mytkalyk
如果这个方法对某些人不起作用,请确保你使用了 myFragment.show(fragmentManager, "TAG"); 来显示你的片段。 - Sujit
显示剩余2条评论

45

这是我的答案。虽然有点晚,但也许对路过的某个人有所帮助。

FragmentManager fm = getFragmentManager();

YourDialogFragment dialog = new YourDialogFragment();
dialog.show(fm,"MyDialog");

fm.executePendingTransactions();
dialog.getDialog().setOnDismissListener(new DialogInterface.OnDismissListener() {
                    @Override
                    public void onDismiss(DialogInterface dialogInterface) {
                       //do whatever you want when dialog is dismissed
                    }
                });

我们需要打电话。

fm.executePendingTransactions(); 
为确保已执行FragmentTransaction工作。否则在调用setOnDismissListener()时会出现NullPointerException异常。
如果有任何错误,敬请原谅。希望这能帮到你。

@Simas:您可以详细说明一下“不起作用”的具体情况吗?是编译错误还是运行时问题? - LarsH
据我所记得,调用fm.executePendingTransactions();并不能像这个答案所说的那样保护免受NPE的影响。 - Simas
4
看起来是一个干净的解决方案,但在屏幕旋转时,它将调用 onDismiss 回调函数,这可能会触发不需要的逻辑,假设这个逻辑应该在用户执行对话框中的某些操作后才运行,然后再调用 dismiss() - Max Ch
2
官方文档指出,与其调用show()然后再调用executePendingTransactions(),不如直接调用惯用的showNow()更为简洁。 - Hiro
当设置对话框的监听器时,像这样设置dismiss()方法似乎不会在对话框片段本身被dismiss()时被调用,而这是推荐的做法。 - Hiro
显示剩余2条评论

15

这是一个老问题,但我找不到满意的解决方案。我不喜欢将任何监听器传递给我的DialogFragment或设置TargetFragment,因为在屏幕方向改变时可能会出现错误。你对此有什么看法?

        MyDialog d = new MyDialog();
        d.show(fragmentManager, "tag");
        fragmentManager.registerFragmentLifecycleCallbacks(new FragmentManager.FragmentLifecycleCallbacks() {
            @Override
            public void onFragmentViewDestroyed(FragmentManager fm, Fragment f) {
                super.onFragmentViewDestroyed(fm, f);
                //do sth      
        fragmentManager.unregisterFragmentLifecycleCallbacks(this);
                }
            }, false);

8

如果您无法访问活动的onDismiss方法,则可以选择另一种解决方案。

//DIALOGFRAGMENT
//Create interface in your DialogFragment (or a new file)
public interface OnDismissListener {
   void onDismiss(MyDialogFragment myDialogFragment);
}
//create Pointer and setter to it
private OnDismissListener onDismissListener;
public void setDissmissListener(DissmissListener dissmissListener) {
   this.dissmissListener = dissmissListener;
}
//Call it on the dialogFragment onDissmiss
@Override
public void onDismiss(DialogInterface dialog) {
   super.onDismiss(dialog);

   if (onDismissListener != null) {
      onDismissListener.onDismiss(this);
   }
}

//OTHER CLASS, start fragment where you want
MyDialogFragment df = new MyDialogFragment();
df.setOnDismissListener(new MyDialogFragment.OnDismissListener() {
   @Override
   public void onDismiss(MyDialogFragment myDialogFragment) {
      //Call when MyDialogFragment close
   }
});
df.show(activity.getFragmentManager(), "myDialogFragment");

编辑: 如果系统需要重新创建DialogFragment: 您可以使用以下方法找到它:

MyDialogFragment myDialogFragment = getFragmentManager().findFragmentByTag("MyDialogFragment"); 
if(myDialogFragment != null) { 
   myDialogFragment.setOnDismissListener(...); 
}

我曾经这样做并认为自己很聪明,但这是危险的。如果框架决定重新创建对话框(由于配置更改或屏幕旋转),它将使用空对话框片段构造函数,并且onDismissListener将不会被设置。 - Aphex
是的,你说得对。将以下代码添加到你的Activity的onCreate方法中: MyDialogFragment myDialogFragment = getFragmentManager().findFragmentByTag("MyDialogFragment"); if(myDialogFragment != null) { myDialogFragment.setOnDismissListener(...); } - Anthone
当然,在dialogFragment.show(getFragmentManager(), "MyDialogFragment")中也要添加相同的标签。 - Anthone

4
public class OpcoesProdutoDialogo extends DialogFragment{
    private DialogInterface.OnDismissListener onDismissOuvinte;
.
.
.

@Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);
        if(onDismissOuvinte!=null)
            onDismissOuvinte.onDismiss(dialog);
    }

    public void setOnDismissListener(@Nullable DialogInterface.OnDismissListener listener) {
        this.onDismissOuvinte = listener;
    }
}

和呼叫中心相关
OpcoesProdutoDialogo opcProduto = OpcoesProdutoDialogo.criar(itemPedido);
        opcProduto.show(getFragmentManager(), "opc_produto_editar");
        opcProduto.setOnDismissListener(d->{
            adapterItens.notifyItemChanged(posicao);
        });

2

使用Kotlin和额外的接口解决方案。(这里将展示一个片段的示例,但稍作更改后也可以在活动中使用)

首先需要创建一个接口(参数集可以是任何内容):

interface DialogCloseListener {
    fun handleDialogClose(dialog: DialogInterface)
}

然后在调用DailogFragment的片段中实现此接口:

class YourParentFragment: Fragment(), DialogCloseListener {
override fun handleDialogClose(dialog: DialogInterface) {
// do something
}
}

现在转到您的DialogFragment。实现onDismiss方法。在其中,检查父片段是否实现了您的接口,调用您的方法,并将必要的参数传递给那里:

    override fun onDismiss(dialog: DialogInterface) {
        super.onDismiss(dialog)
        if(parentFragment is DialogCloseListener){
            (parentFragment as DialogCloseListener).handleDialogClose(dialog)
        }
    }

我认为这种方法很好,因为你可以通过向方法传递特定的参数来跟踪特定的关闭事件,例如取消订单,并进行相应处理。


1
你可以创建DialogFragment的子类并提供自己的监听器,当取消操作发生时会调用该监听器中的onCancel函数。
var onDismissListener: (() -> Unit)? = null

对于不熟悉 Kotlin 的人来说,这只是在 Java 中保存样板接口的匿名接口。在 Java 中使用字段和 setter。

然后在 onCancel 中执行。

    override fun onCancel(dialog: DialogInterface?) {
    super.onCancel(dialog)
    onDismissListener?.invoke()
}

玩得开心!


1

Kotlin答案

private fun showMyCustomDialog() {

    // Show.
    MyCustomDialogFragment().show(fm, "MyCustomDialogFragment")
    
    // Set pending transactions.
    fm.executePendingTransactions()
    
    // Listen dialog closing.
    MyCustomDialogFragment().dialog?.setOnDismissListener { 
        
        // You can do you job when it closed.
    }
}

1

注意:所有示例都不正确,因为您的片段应具有无参数构造函数!

在片段本身中使用后退手势和关闭按钮的有效代码。我删除了一些无用的代码,例如在onCreate中获取参数等。

重要提示:onDismiss也会在方向更改时调用,因此作为结果您应该在回调中检查上下文是否不为空(或使用其他内容)。

public class MyDialogFragment extends DialogFragment {
    
    public static String TAG = "MyFragment";

    public interface ConfirmDialogCompliant {
        void doOkConfirmClick();
    }

    
    public MyFragment(){
        super();
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View rootView = inflater.inflate(R.layout.fragment_layout, container, false);

        ((ImageButton) rootView.findViewById(R.id.btn_close)).setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                // close fragment
                dismiss();
            }
        });
        return rootView;
    }

    @Override
    public void onDismiss(@NonNull DialogInterface dialog) {
        super.onDismiss(dialog);
        // notify
        if(caller != null)
           caller.doOkConfirmClick();
        }
    }

    public void setCallback(ConfirmDialogCompliant caller) {
        this.caller = caller;
    }

    public static MyDialogFragment newInstance(String id) {
        MyDialogFragment f = new MyDialogFragment();

        // Supply num input as an argument.
        Bundle args = new Bundle();
        args.putString("YOU_KEY", id);
        f.setArguments(args);

        return f;
    }
}

现在讲述如何从父级调用它。
MyDialogFragment.ConfirmDialogCompliant callback = new MyDialogFragment.ConfirmDialogCompliant() {

            @Override
            public void doOkConfirmClick() {
                // context can be null, avoid NPE
                if(getContext() != null){

                }

            }

        };

    MyDialogFragment fragment = MyDialogFragment.newInstance("item");
    fragment.setCallback(callback);
    fragment.show(ft, MyDialogFragment.TAG);
        new MyDialogFragment(callback, item);

    fragment.show(getActivity().getSupportFragmentManager(), MyDialogFragment.TAG);
   

附加来源:https://developer.android.com/reference/android/app/DialogFragment

1
如果您不喜欢@yaroslav-mytkalyk的解决方案,其中片段需要转换活动/父片段,这里是另一个解决方案:
以下是想法:
1.在您的片段DialogFragmentImage中公开侦听器。
2.在活动中实现侦听器并在创建片段时将其传递给片段。确保使用标签以便稍后找到片段(请参阅下文)。
3.在onStop()中删除侦听器,以免在销毁活动时泄漏活动。当屏幕旋转时,这将发生,因为活动将被重新创建。
4.在onResume()中检查片段是否存在,如果存在,则重新添加侦听器。

从您的片段中公开一个监听器:

class MyFragment extends DialogFragment {

    public interface OnDismissListener {
        void dismissed();
    }

    @Nullable
    private OnDismissListener onDismissListener;

    public void setOnDismissListener(@Nullable OnDismissListener onDismissListener) {
        this.onDismissListener = onDismissListener;
    }

    /*
    If you are calling dismiss() or dismissAllowingStateLoss() manually,
    don't forget to call:
    if (onDismissListener != null) {
        onDismissListener.dismissed();
    }

    Otherwise, override them and call it there.
    */
}

这是您的活动应该看起来的样子:

class MyActivity extends AppCompatActivity {

    private static final String MY_FRAGMENT_TAG = "my_fragment";

    private MyFragment.OnDismissListener myFragmentListener = () -> {

        // ...
    };

    /**
     * Shows the fragment. Note that:
     * 1. We pass a tag to `show()`.
     * 2. We set the listener on the fragment.
     */
    private void showFragment() {

        MyFragment fragment = new MyFragment();
        fragment.show(getSupportFragmentManager(), MY_FRAGMENT_TAG);
        fragment.setOnDismissListener(myFragmentListener);
    }

    @Override
    protected void onStart() {

        super.onStart();

        // Restore the listener that we may have removed in `onStop()`.
        @Nullable MyFragment myFragment =  (MyFragment) getSupportFragmentManager().findFragmentByTag(MY_FRAGMENT_TAG);
        if (myFragment != null) {
            myFragment.setOnDismissListener(myFragmentListener);
        }
    }

    @Override
    protected void onStop() {

        // If the fragment is currently shown, remove the listener so that the activity is not leaked when e.g. the screen is rotated and it's re-created.
        @Nullable MyFragment myFragment =  (MyFragment) getSupportFragmentManager().findFragmentByTag(MY_FRAGMENT_TAG);
        if (myFragment != null) {
            myFragment.setOnDismissListener(null);
        }

        super.onStop();
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接