Android DialogFragment与对话框


244

Google建议我们使用DialogFragment而不是简单DialogFragments API,但是DialogFragment对于简单的“是-否”确认消息框使用隔离符是荒谬的。在这种情况下,最佳做法是什么?


5
简而言之,除其他外,简单DialogAlertDialog.Builder::create()::show()将创建一个对话框,当您旋转屏幕时该对话框会消失。
user1032613 '18年

Answers:


83

是的,使用DialogFragment和,onCreateDialog您仍然可以简单地使用AlertDialog构建器AlertDialog通过“是/否”确认按钮来创建简单的对话框。根本没有太多的代码。

关于处理片段中的事件,可以有多种方法,但我只需Handler在自己的消息中定义一条消息,然后通过其构造函数FragmentDialogFragment其传递到via中,然后根据各种click事件,将消息传递回片段的处理程序。同样,这样做的方法也多种多样,但以下对我有用。

在对话框中,保存一条消息并在构造函数中实例化它:

private Message okMessage;
...
okMessage = handler.obtainMessage(MY_MSG_WHAT, MY_MSG_OK);

onClickListener在您的对话框中实现,然后根据需要调用处理程序:

public void onClick(.....
    if (which == DialogInterface.BUTTON_POSITIVE) {
        final Message toSend = Message.obtain(okMessage);
        toSend.sendToTarget();
    }
 }

编辑

作为Message包裹,您可以将其保存onSaveInstanceState并还原

outState.putParcelable("okMessage", okMessage);

然后在 onCreate

if (savedInstanceState != null) {
    okMessage = savedInstanceState.getParcelable("okMessage");
}

4
问题不是okMes​​sage-问题是okMes​​sage target,如果您从Bundle中加载,它将为null。如果Message的目标为null,并且您使用sendToTarget,则会得到NullPointerException-不是因为Message为null,而是因为它的目标是。
hrnt 2011年

2
使用DialogFragment代替Dialog有什么好处?
拉斐尔·佩特格罗索

79
使用DialogFragment的优点是将为您处理对话框的所有生命周期。您将再也不会收到错误“对话框已泄漏...”。转到DialogFragment并忘记对话框。
Snicolas 2013年

6
我认为应该使用setArguments()和getArguments()代替通过构造函数传递okMes​​sage。
pjv 2013年

1
好吧,我非常轻松地使用了Builder,我使用这个android:configChanges =“ locale | keyboardHidden | orientation | screenSize”处理活动管理,我在应用程序中没有看到任何问题...
Renetik,2015年

67

您可以创建通用的DialogFragment子类,例如YesNoDialog和OkDialog,如果在应用程序中经常使用对话框,则可以传入标题和消息。

public class YesNoDialog extends DialogFragment
{
    public static final String ARG_TITLE = "YesNoDialog.Title";
    public static final String ARG_MESSAGE = "YesNoDialog.Message";

    public YesNoDialog()
    {

    }

    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState)
    {
        Bundle args = getArguments();
        String title = args.getString(ARG_TITLE);
        String message = args.getString(ARG_MESSAGE);

        return new AlertDialog.Builder(getActivity())
            .setTitle(title)
            .setMessage(message)
            .setPositiveButton(android.R.string.yes, new DialogInterface.OnClickListener()
            {
                @Override
                public void onClick(DialogInterface dialog, int which)
                {
                    getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, null);
                }
            })
            .setNegativeButton(android.R.string.no, new DialogInterface.OnClickListener()
            {
                @Override
                public void onClick(DialogInterface dialog, int which)
                {
                    getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_CANCELED, null);
                }
            })
            .create();
    }
}

然后使用以下命令调用它:

    DialogFragment dialog = new YesNoDialog();
    Bundle args = new Bundle();
    args.putString(YesNoDialog.ARG_TITLE, title);
    args.putString(YesNoDialog.ARG_MESSAGE, message);
    dialog.setArguments(args);
    dialog.setTargetFragment(this, YES_NO_CALL);
    dialog.show(getFragmentManager(), "tag");

并在中处理结果onActivityResult


是的,DialogFragment为您处理所有生命周期事件。
ashishduh 2014年

1
我认为这不是因为旋转后旧的Dialog仍然存在,并且它将分配给不存在的旧片段(dialog.setTargetFragment(this,YES_NO_CALL);),所以旋转后getTargetFragment()。onActivityResult不起作用
Malachiasz

7
是什么YES_NO_CALLgetFragmentManager()onActivityResult
msysmilu 2015年

2
YES_NO_CALL是一个自定义int,它是请求代码。 getFragmentManager()获取活动的片段管理器,并且 onActivityResult()是片段生命周期回调方法。
ashishduh 2015年

3
将getFragmentManager()替换为getSupportFragmentManager();
Avinash Verma

33

在AlertDialog上使用DialogFragment:


  • 自从引入API级别13以来

    不推荐使用 Activity中的showDialog方法。不建议在代码中的其他地方调用对话框,因为您必须自己管理对话框(例如,方向更改)。

  • 差异DialogFragment-AlertDialog

    它们有很大不同吗?来自Android的有关DialogFragment的参考:

    DialogFragment是一个片段,它显示一个对话框窗口,浮动在其活动窗口的顶部。该片段包含一个Dialog对象,它根据片段的状态适当显示。对话框的控制(决定何时显示,隐藏,关闭它)应通过此处的API进行,而不是直接在对话框上进行调用。

  • 其他注意事项

    • 由于屏幕尺寸不同的设备的多样性,片段是Android框架中的自然演变。
    • DialogFragments和Fragments在支持库中可用,这使得该类可在所有当前使用的Android版本中使用。

28

我建议使用DialogFragment

当然,考虑到这是一个非常简单的任务,使用它创建“是/否”对话框非常复杂,但是使用对话框创建类似对话框Dialog也非常复杂。

(活动生命周期使其变得复杂-您必须Activity管理对话框的生命周期- Activity.showDialog如果使用低于8的API级别,则无法将自定义参数(例如自定义消息)传递给)

令人高兴的是,您通常可以DialogFragment很容易地构建自己的抽象。


您将如何处理警报对话框回调(是,否)?
Alexey Zakharov

最简单的方法是在托管Activity中实现一个带有String参数的方法。例如,当用户单击“是”时,对话框将使用参数“同意”来调用活动的方法。这些参数在显示对话框时指定,例如AskDialog.ask(“您同意这些条款吗?”,“同意”,“不同意”);
hrnt 2011年

5
但是我需要片段内的回调,而不是活动。我可以使用setTargetFragment并将其强制转换为接口。但这是地狱。
Alexey Zakharov

您还可以通过标签设置为目标,并使用获取目标片段FragmentManagerfindFragmentByTag。但是,是的,它需要一些代码。
hrnt 2011年

@AlexeyZakharov我知道这要晚5年了,但您可以通过Fragment this 并拥有您Activity extends的证件Interface。不过,请谨慎对待线程,如果并发检查没有必要,您可能会在不需要接口调用时弹出接口调用。虽然不确定这与内存和循环依赖意大利面条有什么关系,还有其他人想插入吗?另一个选项是Message/,Handler但是您仍然可能存在并发问题。
技巧论

8

具有生成器模式的通用AlertDialogFragment

在我的项目,我已经习惯AlertDialog.Builder已经很多之前,我发现,这是有问题的。但是,我不想在我的应用程序中的任何地方更改那么多的代码。此外,我实际上是喜欢将OnClickListeners匿名类传递到需要它们的地方(也就是说,在使用setPositiveButton()setNegativeButton()等)而不必实现数千个回调方法来在对话框片段和持有者片段之间进行通信,我认为,这会导致非常混乱和复杂的代码。特别是,如果您在一个片段中有多个不同的对话框,然后需要在回调实现中区分当前显示的是哪个对话框。

因此,我结合了不同的方法来创建通用的AlertDialogFragment帮助程序类,该类可以完全一样 地使用AlertDialog


请注意,我在代码中使用的是Java 8 lambda表达式,因此,如果您尚未使用lambda表达式,则可能必须更改部分代码。)

/**
 * Helper class for dialog fragments to show a {@link AlertDialog}. It can be used almost exactly
 * like a {@link AlertDialog.Builder}
 * <p />
 * Creation Date: 22.03.16
 *
 * @author felix, http://flx-apps.com/
 */
public class AlertDialogFragment extends DialogFragment {
    protected FragmentActivity activity;
    protected Bundle args;
    protected String tag = AlertDialogFragment.class.getSimpleName();

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        activity = getActivity();
        args = getArguments();
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Dialog dialog = setDialogDefaults(new AlertDialog.Builder(getActivity())).create();

        if (args.containsKey("gravity")) {
            dialog.getWindow().getAttributes().gravity = args.getInt("gravity");
        }

        dialog.setOnShowListener(d -> {
            if (dialog != null && dialog.findViewById((android.R.id.message)) != null) {
                ((TextView) dialog.findViewById(android.R.id.message)).setMovementMethod(LinkMovementMethod.getInstance());
            }
        });
        return dialog;
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return super.onCreateView(inflater, container, savedInstanceState);
    }

    @Override
    public void onDismiss(DialogInterface dialog) {
        super.onDismiss(dialog);

        if (args.containsKey("onDismissListener")) {
            Parcelable onDismissListener = args.getParcelable("onDismissListener");
            if (onDismissListener != null && onDismissListener instanceof ParcelableOnDismissListener) {
                ((ParcelableOnDismissListener) onDismissListener).onDismiss(this);
            }
        }
    }

    /**
     * Sets default dialog properties by arguments which were set using {@link #builder(FragmentActivity)}
     */
    protected AlertDialog.Builder setDialogDefaults(AlertDialog.Builder builder) {
        args = getArguments();
        activity = getActivity();

        if (args.containsKey("title")) {
            builder.setTitle(args.getCharSequence("title"));
        }

        if (args.containsKey("message")) {
            CharSequence message = args.getCharSequence("message");
            builder.setMessage(message);
        }

        if (args.containsKey("viewId")) {
            builder.setView(getActivity().getLayoutInflater().inflate(args.getInt("viewId"), null));
        }

        if (args.containsKey("positiveButtonText")) {
            builder.setPositiveButton(args.getCharSequence("positiveButtonText"), (dialog, which) -> {
                onButtonClicked("positiveButtonListener", which);
            });
        }

        if (args.containsKey("negativeButtonText")) {
            builder.setNegativeButton(args.getCharSequence("negativeButtonText"), (dialog, which) -> {
                onButtonClicked("negativeButtonListener", which);
            });
        }

        if (args.containsKey("neutralButtonText")) {
            builder.setNeutralButton(args.getCharSequence("neutralButtonText"), (dialog, which) -> {
                onButtonClicked("neutralButtonListener", which);
            });
        }

        if (args.containsKey("items")) {
            builder.setItems(args.getStringArray("items"), (dialog, which) -> {
                onButtonClicked("itemClickListener", which);
            });
        }

        // @formatter:off
        // FIXME this a pretty hacky workaround: we don't want to show the dialog if onClickListener of one of the dialog's button click listener were lost
        //       the problem is, that there is no (known) solution for parceling a OnClickListener in the long term (only for state changes like orientation change,
        //       but not if the Activity was completely lost)
        if (
                (args.getParcelable("positiveButtonListener") != null && !(args.getParcelable("positiveButtonListener") instanceof ParcelableOnClickListener)) ||
                (args.getParcelable("negativeButtonListener") != null && !(args.getParcelable("negativeButtonListener") instanceof ParcelableOnClickListener)) ||
                (args.getParcelable("neutralButtonListener") != null && !(args.getParcelable("neutralButtonListener") instanceof ParcelableOnClickListener)) ||
                (args.getParcelable("itemClickListener") != null && !(args.getParcelable("itemClickListener") instanceof ParcelableOnClickListener))
        ) {
            new DebugMessage("Forgot onClickListener. Needs to be dismissed.")
                    .logLevel(DebugMessage.LogLevel.VERBOSE)
                    .show();
            try {
                dismissAllowingStateLoss();
            } catch (NullPointerException | IllegalStateException ignored) {}
        }
        // @formatter:on

        return builder;
    }

    public interface OnDismissListener {
        void onDismiss(AlertDialogFragment dialogFragment);
    }

    public interface OnClickListener {
        void onClick(AlertDialogFragment dialogFragment, int which);
    }

    protected void onButtonClicked(String buttonKey, int which) {
        ParcelableOnClickListener parcelableOnClickListener = getArguments().getParcelable(buttonKey);
        if (parcelableOnClickListener != null) {
            parcelableOnClickListener.onClick(this, which);
        }
    }

    // region Convenience Builder Pattern class almost similar to AlertDialog.Builder
    // =============================================================================================

    public AlertDialogFragment builder(FragmentActivity activity) {
        this.activity = activity;
        this.args = new Bundle();
        return this;
    }

    public AlertDialogFragment addArguments(Bundle bundle) {
        args.putAll(bundle);
        return this;
    }

    public AlertDialogFragment setTitle(int titleStringId) {
        return setTitle(activity.getString(titleStringId));
    }

    public AlertDialogFragment setTitle(CharSequence title) {
        args.putCharSequence("title", title);
        return this;
    }

    public AlertDialogFragment setMessage(int messageStringId) {
        return setMessage(activity.getString(messageStringId));
    }

    public AlertDialogFragment setMessage(CharSequence message) {
        args.putCharSequence("message", message);
        return this;
    }

    public AlertDialogFragment setPositiveButton(int textStringId, OnClickListener onClickListener) {
        return setPositiveButton(activity.getString(textStringId), onClickListener);
    }

    public AlertDialogFragment setPositiveButton(CharSequence text, AlertDialogFragment.OnClickListener onClickListener) {
        args.putCharSequence("positiveButtonText", text);
        args.putParcelable("positiveButtonListener", createParcelableOnClickListener(onClickListener));
        return this;
    }

    public AlertDialogFragment setNegativeButton(int textStringId, AlertDialogFragment.OnClickListener onClickListener) {
        return setNegativeButton(activity.getString(textStringId), onClickListener);
    }

    public AlertDialogFragment setNegativeButton(CharSequence text, AlertDialogFragment.OnClickListener onClickListener) {
        args.putCharSequence("negativeButtonText", text);
        args.putParcelable("negativeButtonListener", createParcelableOnClickListener(onClickListener));
        return this;
    }

    public AlertDialogFragment setNeutralButton(int textStringId, AlertDialogFragment.OnClickListener onClickListener) {
        return setNeutralButton(activity.getString(textStringId), onClickListener);
    }

    public AlertDialogFragment setNeutralButton(CharSequence text, AlertDialogFragment.OnClickListener onClickListener) {
        args.putCharSequence("neutralButtonText", text);
        args.putParcelable("neutralButtonListener", createParcelableOnClickListener(onClickListener));
        return this;
    }

    public AlertDialogFragment setOnDismissListener(OnDismissListener onDismissListener) {
        if (onDismissListener == null) {
            return this;
        }

        Parcelable p = new ParcelableOnDismissListener() {
            @Override
            public void onDismiss(AlertDialogFragment dialogFragment) {
                onDismissListener.onDismiss(dialogFragment);
            }
        };
        args.putParcelable("onDismissListener", p);
        return this;
    }

    public AlertDialogFragment setItems(String[] items, AlertDialogFragment.OnClickListener onClickListener) {
        args.putStringArray("items", items);
        args.putParcelable("itemClickListener", createParcelableOnClickListener(onClickListener));
        return this;
    }

    public AlertDialogFragment setView(int viewId) {
        args.putInt("viewId", viewId);
        return this;
    }

    public AlertDialogFragment setGravity(int gravity) {
        args.putInt("gravity", gravity);
        return this;
    }

    public AlertDialogFragment setTag(String tag) {
        this.tag = tag;
        return this;
    }

    public AlertDialogFragment create() {
        setArguments(args);
        return AlertDialogFragment.this;
    }

    public AlertDialogFragment show() {
        create();
        try {
            super.show(activity.getSupportFragmentManager(), tag);
        }
        catch (IllegalStateException e1) {

            /**
             * this whole part is used in order to attempt to show the dialog if an
             * {@link IllegalStateException} was thrown (it's kinda comparable to
             * {@link FragmentTransaction#commitAllowingStateLoss()} 
             * So you can remove all those dirty hacks if you are sure that you are always
             * properly showing dialogs in the right moments
             */

            new DebugMessage("got IllegalStateException attempting to show dialog. trying to hack around.")
                    .logLevel(DebugMessage.LogLevel.WARN)
                    .exception(e1)
                    .show();

            try {
                Field mShownByMe = DialogFragment.class.getDeclaredField("mShownByMe");
                mShownByMe.setAccessible(true);
                mShownByMe.set(this, true);
                Field mDismissed = DialogFragment.class.getDeclaredField("mDismissed");
                mDismissed.setAccessible(true);
                mDismissed.set(this, false);
            }
            catch (Exception e2) {
                new DebugMessage("error while showing dialog")
                        .exception(e2)
                        .logLevel(DebugMessage.LogLevel.ERROR)
                        .show();
            }
            FragmentTransaction transaction = activity.getSupportFragmentManager().beginTransaction();
            transaction.add(this, tag);
            transaction.commitAllowingStateLoss(); // FIXME hacky and unpredictable workaround
        }
        return AlertDialogFragment.this;
    }

    @Override
    public int show(FragmentTransaction transaction, String tag) {
        throw new NoSuchMethodError("Please use AlertDialogFragment.show()!");
    }

    @Override
    public void show(FragmentManager manager, String tag) {
        throw new NoSuchMethodError("Please use AlertDialogFragment.show()!");
    }

    protected ParcelableOnClickListener createParcelableOnClickListener(AlertDialogFragment.OnClickListener onClickListener) {
        if (onClickListener == null) {
            return null;
        }

        return new ParcelableOnClickListener() {
            @Override
            public void onClick(AlertDialogFragment dialogFragment, int which) {
                onClickListener.onClick(dialogFragment, which);
            }
        };
    }

    /**
     * Parcelable OnClickListener (can be remembered on screen rotation)
     */
    public abstract static class ParcelableOnClickListener extends ResultReceiver implements AlertDialogFragment.OnClickListener {
        public static final Creator<ResultReceiver> CREATOR = ResultReceiver.CREATOR;

        ParcelableOnClickListener() {
            super(null);
        }

        @Override
        public abstract void onClick(AlertDialogFragment dialogFragment, int which);
    }

    /**
     * Parcelable OnDismissListener (can be remembered on screen rotation)
     */
    public abstract static class ParcelableOnDismissListener extends ResultReceiver implements AlertDialogFragment.OnDismissListener {
        public static final Creator<ResultReceiver> CREATOR = ResultReceiver.CREATOR;

        ParcelableOnDismissListener() {
            super(null);
        }

        @Override
        public abstract void onDismiss(AlertDialogFragment dialogFragment);
    }


    // =============================================================================================
    // endregion
}

用法

// showing a normal alert dialog with state loss on configuration changes (like device rotation)
new AlertDialog.Builder(getActivity())
        .setTitle("Are you sure? (1)")
        .setMessage("Do you really want to do this?")
        .setPositiveButton("Yes", (dialog, which) -> Toast.makeText(getContext(), "Yes clicked", Toast.LENGTH_SHORT).show())
        .setNegativeButton("Cancel", null)
        .show();

// showing a dialog fragment using the helper class with no state loss on configuration changes
new AlertDialogFragment.builder(getActivity())
        .setTitle("Are you sure? (2)")
        .setMessage("Do you really want to do this?")
        .setPositiveButton("Yes", (dialog, which) -> Toast.makeText(getContext(), "Yes clicked", Toast.LENGTH_SHORT).show())
        .setNegativeButton("Cancel", null)
        .show();

我将其发布在这里不仅是为了分享我的解决方案,还因为我想征询您的意见:这种方法在某种程度上合法或有问题吗?


3
这是一个非常有趣的想法,但我认为API设计无效。如果将OnClickListener传递给setPositiveButton(),则当旋转设备并从Bundle args重新创建片段时,将不会从Parcelable中正确创建OnClickListeners。根本的问题是,您不能在旋转期间重新创建侦听器,但是API接口(带有接口)需要它。我希望事实并非如此(因为我喜欢这个想法)。
Xargs

1
好主意,但正如@Xargs所说,它不起作用。旋转时无法正确重新创建传入的侦听器。
Graham Borland

我的结果是,它实际上可以轮换使用,并且可以恢复到应用程序(例如,进入主屏幕后),但是在活动被完全销毁后恢复活动(然后OnClickListeners确实丢失)后,恢复活动无效。(已在Android 4.4.4和Android 5.1.1上测试)
flxapps

我还没有测试这个确切的实现,但是从我测试的结果来看,传递给fragment bundle的可打包侦听器在重新创建时被正确调用。我不知道为什么,但似乎可行。
Saad Farooq

@flxapps,在自定义视图的情况下,如何获取子视图并更改其属性或应用侦听器?在您的课堂上,您不会返回任何对话框实例,并且如果有人尝试获取子视图,则可能会导致异常
Zubair Rehman '18

5

我可以建议对@ashishduh的答案进行一些简化:

public class AlertDialogFragment extends DialogFragment {
public static final String ARG_TITLE = "AlertDialog.Title";
public static final String ARG_MESSAGE = "AlertDialog.Message";

public static void showAlert(String title, String message, Fragment targetFragment) {
    DialogFragment dialog = new AlertDialogFragment();
    Bundle args = new Bundle();
    args.putString(ARG_TITLE, title);
    args.putString(ARG_MESSAGE, message);
    dialog.setArguments(args);
    dialog.setTargetFragment(targetFragment, 0);
    dialog.show(targetFragment.getFragmentManager(), "tag");
}

public AlertDialogFragment() {}

@NonNull
@Override
public AlertDialog onCreateDialog(Bundle savedInstanceState)
{
    Bundle args = getArguments();
    String title = args.getString(ARG_TITLE, "");
    String message = args.getString(ARG_MESSAGE, "");

    return new AlertDialog.Builder(getActivity())
            .setTitle(title)
            .setMessage(message)
            .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener()
            {
                @Override
                public void onClick(DialogInterface dialog, int which)
                {
                    getTargetFragment().onActivityResult(getTargetRequestCode(), Activity.RESULT_OK, null);
                }
            })
            .create();
}

它消除了(类的)用户熟悉组件内部的需要,并使使用变得非常简单:

AlertDialogFragment.showAlert(title, message, this);

PS:就我而言,我需要一个简单的警报对话框,这就是我创建的。您可以将方法应用于是/否或所需的任何其他类型。


1

使用对话框可进行简单的是或否对话框。

当您需要更复杂的视图来掌握生命周期(例如oncreate,请求权限)时,任何生命周期覆盖都将使用对话框片段。因此,您可以将权限和对话框需要运行的其他任何代码分开,而不必与调用活动进行通信。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.