谷歌建议我们使用DialogFragment
而非简单的Dialog
,使用Fragments API
,但在处理简单的是/否确认信息时,使用孤立的DialogFragment
是荒谬的。在这种情况下,最佳实践是什么?
谷歌建议我们使用DialogFragment
而非简单的Dialog
,使用Fragments API
,但在处理简单的是/否确认信息时,使用孤立的DialogFragment
是荒谬的。在这种情况下,最佳实践是什么?
是的,使用DialogFragment
,在onCreateDialog
中,您可以简单地使用AlertDialog构建器来创建一个带有“是/否”确认按钮的简单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");
}
target
属性会变成 null。如果一个 Message 的 target 是 null,并且你使用了 sendToTarget
方法,你会得到一个 NullPointerException 异常,这不是因为 Message 本身为 null,而是因为它的 target 为 null。 - hrnt如果你在应用程序中经常使用对话框,可以创建一般的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
中处理结果。
YES_NO_CALL
是什么,getFragmentManager()
和onActivityResult
又分别是什么?YES_NO_CALL
:无法确定具体指的是哪个概念或者上下文,请提供更多信息。getFragmentManager()
:这是一个Android应用编程接口(API),用于获取当前Activity的FragmentManager对象。FragmentManager用于管理Fragment,可以在Activity中添加、删除和替换Fragment。onActivityResult
:这是一个回调方法,当启动的Activity完成后,通过该方法向发起Activity传递数据。通常情况下,在发起Activity中使用startActivityForResult()
方法启动其他Activity,当被启动的Activity完成后会返回结果,并在发起Activity中触发onActivityResult()
方法来处理结果。YES_NO_CALL
是一个自定义整数,用作请求代码。getFragmentManager()
获取活动的片段管理器,而 onActivityResult()
则是片段生命周期回调方法。 - ashishduh自API级别13引入以来:
Activity的showDialog方法已被弃用。 在其他代码中调用对话框不建议,因为您将不得不自己管理对话框(例如屏幕方向更改)。
DialogFragment和AlertDialog的区别
它们有很大的区别吗?从Android参考文献中关于DialogFragment的描述:
DialogFragment是一个显示在其活动窗口上方的对话框窗口片段。此片段包含一个Dialog对象,根据片段的状态适当地显示该对象。应通过此处提供的API 而不是直接调用对话框来控制对话框(决定何时显示、隐藏、取消对话框)。
其他注意事项
我建议使用DialogFragment
。
当然,使用它创建一个“是/否”对话框相当复杂,考虑到这应该是一个相当简单的任务,但是使用Dialog
创建类似的对话框也出奇地复杂。
(活动生命周期使得它变得复杂 - 必须让Activity
管理对话框的生命周期 - 如果使用8以下的API级别,则无法通过Activity.showDialog
传递自定义参数,例如自定义消息)
好处是,通常可以很容易地在DialogFragment
之上构建自己的抽象。
String
参数的方法。例如,当用户点击“Yes”时,对话框将调用活动的方法并传递“agree”参数。这些参数是在显示对话框时指定的,例如AskDialog.ask("Do you agree to these terms?", "agree", "disagree");。 - hrntFragmentManager
的findFragmentByTag
来获取目标片段。但是,这需要相当多的代码。 - hrntFragment
的this
传递,并让你的Activity
实现你的Interface
。但要小心线程问题,如果并发没有得到控制,你可能会在不需要它们时弹出接口调用。不确定这对内存和循环依赖关系有什么影响,还有其他人想加入讨论吗?另一个选择是使用Message
/ Handler
,但你仍然可能会遇到并发问题。 - tricknologyDialogFragment基本上是一个可以用作对话框的Fragment。
出于以下原因,使用DialogFragment而不是Dialog:
- DialogFragment在配置更改和保存恢复流程后会自动重新创建
- DialogFragment继承了完整的Fragment生命周期
- 不再出现IllegalStateExceptions和泄露窗口崩溃。当活动被销毁时,警告对话框仍然存在这种情况很常见。
我建议对 @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);
P.S. 在我的情况下,我需要一个简单的警示对话框,所以我创建了这个。您可以将此方法应用于您需要的“是/否”或任何其他类型。
在我的项目中,我已经在很多地方使用了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();
我不仅在这里发布我的解决方案,还想问你们的意见:这种方法是否合理或存在问题?
使用 Dialog 进行简单的是或否对话框。
当你需要更复杂的视图,并且需要获取生命周期,比如 oncreate、请求权限、任何生命周期覆盖时,建议使用对话框片段(Dialog Fragment)。这样可以将权限和任何其他代码分离,使得对话框能够正常运作而无需与调用活动通信。
对话框:对话框是一个小窗口,提示用户进行决策或输入其他信息。
DialogFragment:DialogFragment是一种特殊的片段子类,专门用于创建和托管对话框。它允许FragmentManager管理对话框的状态,并在配置更改发生时自动恢复对话框。
DialogFragment具备对话框和片段的功能。基本上,所有的生命周期事件都由DialogFragment自动管理,例如屏幕配置更改等。
Dialog
或AlertDialog.Builder::create()::show()
创建的对话框将在旋转屏幕时消失。 - WSBT