Android DialogFragment vs Dialog

谷歌建议我们使用DialogFragment而不是通过使用Fragments API来使用简单的Dialog,但是使用一个孤立的DialogFragment来作为一个简单的Yes-No确认消息框是荒谬的。在这种情况下,最佳实践是什么?

167369 次浏览

我建议使用DialogFragment

当然,用它创建一个“Yes/No”对话框是相当复杂的,因为它应该是一个相当简单的任务,但是用Dialog创建一个类似的对话框也是惊人地复杂。

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

好的事情是你通常可以很容易地在DialogFragment之上构建你自己的抽象。

是的,使用DialogFragment,在onCreateDialog中,你可以简单地使用AlertDialog构建器来创建一个简单的带有Yes/No确认按钮的AlertDialog。根本没有多少代码。

关于在你的片段中处理事件,会有各种各样的方法,但我只是在我的Fragment中定义了一个消息Handler,通过它的构造函数将它传递到DialogFragment,然后根据各种点击事件将消息传递回我的片段的处理程序。有很多方法,但下面的方法对我来说是有效的。

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

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

在AlertDialog上使用DialogFragment:


  • < p > 自从引入了API级别13:

    Activity中的showDialog方法是弃用。 在代码的其他地方调用对话框是不可取的,因为你必须自己管理对话框(例如方向改变)
  • < p > 差异DialogFragment - AlertDialog

    它们有这么大的不同吗?来自关于DialogFragment的Android引用:

    一个DialogFragment是一个片段,它显示一个对话框窗口,浮在它的上面 活动的窗口。这个片段包含一个Dialog对象 根据片段的状态适当地显示。控制 对话(决定何时显示,隐藏,取消)应该完成

    通过API 在这里,而不是直接调用对话框 李< /引用> < / >
  • < p > 其他的笔记

    • Fragments是Android框架的自然进化,因为不同屏幕大小的设备具有多样性。
    • DialogFragments和Fragments在支持库中可用,这使得该类在当前所有使用的Android版本中可用。
    • 李< / ul > < / >

你可以创建一般的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中处理结果。

通用AlertDialogFragment与生成器模式

在我的项目中,我已经使用AlertDialog.Builder已经很多之前,我发现它是有问题的。然而,我不想在我的应用程序的任何地方改变那么多的代码。此外,我实际上是一个粉丝传递OnClickListeners作为匿名类在他们需要的地方(也就是说,当使用setPositiveButton()setNegativeButton()等),而不是必须实现数以千计的回调方法之间的对话片段和holder片段通信,这可以,在我看来,导致非常混乱和复杂的代码。特别是,如果你在一个片段中有多个不同的对话框,然后需要在回调实现中区分当前正在显示的对话框。

因此,我结合了不同的方法来创建一个通用的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();

我在这里发表这篇文章不仅是为了分享我的解决方案,也是因为我想征求你们的意见:这种方法在某种程度上是合法的还是有问题的?

我可以建议简化一下@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);

附注:在我的情况下,我需要一个简单的警告对话框,所以这就是我所创建的。您可以将该方法应用于Yes/No或您需要的任何其他类型。

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

当你需要更复杂的视图,其中你需要获得生命周期,如oncreate,请求权限,任何生命周期覆盖我将使用一个对话框片段。因此,您可以分离权限和对话框需要操作的任何其他代码,而不必与调用活动通信。

DialogFragment具有对话框和片段的功能。基本上所有的生命周期事件都是由DialogFragment自动管理的,比如屏幕配置的改变等。

DialogFragment基本上是一个可以用作对话框的片段。

使用DialogFragment而不是Dialog的原因如下:

  • 在配置更改后自动重新创建DialogFragment并保存恢复流
  • DialogFragment继承了完整的Fragment生命周期
  • 不再有illegalstateexception和泄漏的窗口崩溃。当活动被警报对话框破坏时,这是非常常见的 李还在那里。< / >

更多详细信息

对话框是一个小窗口,提示用户做出决定或输入额外的信息。

DialogFragment:一个DialogFragment是一个特殊的片段子类,它被设计用来创建和托管对话框。它允许FragmentManager管理对话框的状态,并在发生配置更改时自动恢复对话框。