Câu trả lời:
Có, sử dụng DialogFragment
và trong onCreateDialog
bạn chỉ cần sử dụng trình tạo AlertDialog để tạo một đơn giản AlertDialog
với các nút xác nhận Có / Không. Không có nhiều mã ở tất cả.
Liên quan đến việc xử lý các sự kiện trong đoạn của bạn, sẽ có nhiều cách khác nhau để thực hiện nhưng tôi chỉ cần xác định một thông báo Handler
trong đó Fragment
, chuyển nó DialogFragment
qua hàm tạo của nó và sau đó chuyển tin nhắn lại cho trình xử lý đoạn của tôi như là sự chấp thuận cho các sự kiện nhấp chuột khác nhau. Một lần nữa cách khác nhau để làm điều đó nhưng sau đây làm việc cho tôi.
Trong hộp thoại giữ một thông báo và khởi tạo nó trong hàm tạo:
private Message okMessage;
...
okMessage = handler.obtainMessage(MY_MSG_WHAT, MY_MSG_OK);
Thực hiện onClickListener
trong hộp thoại của bạn và sau đó gọi trình xử lý khi thích hợp:
public void onClick(.....
if (which == DialogInterface.BUTTON_POSITIVE) {
final Message toSend = Message.obtain(okMessage);
toSend.sendToTarget();
}
}
Biên tập
Và như Message
là bưu kiện, bạn có thể lưu nó vào onSaveInstanceState
và khôi phục nó
outState.putParcelable("okMessage", okMessage);
Sau đó trong onCreate
if (savedInstanceState != null) {
okMessage = savedInstanceState.getParcelable("okMessage");
}
target
sẽ không có giá trị nếu bạn tải nó từ Gói. Nếu mục tiêu của Tin nhắn là null và bạn sử dụng sendToTarget
, bạn sẽ nhận được NullPulumException - không phải vì Tin nhắn là null, mà vì mục tiêu của nó là.
Bạn có thể tạo các lớp con DialogFragment chung như YesNoDialog và OkDialog, và chuyển tiêu đề và tin nhắn nếu bạn sử dụng nhiều hộp thoại trong ứng dụng của mình.
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();
}
}
Sau đó gọi nó bằng cách sử dụng như sau:
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");
Và xử lý kết quả trong onActivityResult
.
YES_NO_CALL
, getFragmentManager()
và onActivityResult
?
YES_NO_CALL
là một int tùy chỉnh đó là mã yêu cầu. getFragmentManager()
được người quản lý phân đoạn cho hoạt động và onActivityResult()
là một phương thức gọi lại vòng đời của đoạn.
Kể từ khi giới thiệu API cấp 13 :
các ShowDialog phương pháp từ Hoạt động đang bị phản đối . Không thể gọi hộp thoại ở nơi khác trong mã vì bạn sẽ phải tự mình quản lý hộp thoại (ví dụ: thay đổi hướng).
DialogFragment khác biệt - AlertDialog
Có phải họ rất khác nhau? Từ tài liệu tham khảo Android liên quan đến DialogFragment :
DialogFragment là một đoạn hiển thị một cửa sổ hộp thoại, nổi trên đầu cửa sổ hoạt động của nó. Đoạn này chứa một đối tượng Hộp thoại, nó hiển thị là phù hợp dựa trên trạng thái của đoạn đó. Kiểm soát hộp thoại (quyết định khi nào hiển thị, ẩn, loại bỏ nó) nên được thực hiện thông qua API ở đây , không phải bằng các cuộc gọi trực tiếp trên hộp thoại.
Ghi chú khác
Tôi khuyên bạn nên sử dụng DialogFragment
.
Chắc chắn, việc tạo một hộp thoại "Có / Không" với nó khá phức tạp vì nó khá đơn giản, nhưng việc tạo một hộp thoại tương tự Dialog
cũng phức tạp một cách đáng ngạc nhiên.
(Vòng đời hoạt động làm cho nó phức tạp - bạn phải cho phép Activity
quản lý vòng đời của hộp thoại - và không có cách nào để truyền tham số tùy chỉnh, ví dụ như thông báo tùy chỉnh Activity.showDialog
nếu sử dụng các cấp API dưới 8)
Điều tuyệt vời là bạn thường có thể xây dựng sự trừu tượng của riêng mình trên đầu trang DialogFragment
khá dễ dàng.
String
tham số. Ví dụ, khi người dùng nhấp vào "Có", hộp thoại sẽ gọi phương thức của Activity với tham số "đồng ý". Các tham số này được chỉ định khi hiển thị hộp thoại, ví dụ AskDialog.ask ("Bạn có đồng ý với các điều khoản này không?", "Đồng ý", "không đồng ý");
FragmentManager
's findFragmentByTag
. Nhưng vâng, nó đòi hỏi một chút mã.
Fragment
this
và có Activity
extends
bạn Interface
. Mặc dù cẩn thận khi xâu chuỗi, bạn có thể tắt các cuộc gọi giao diện khi bạn không nhất thiết muốn chúng nếu không kiểm tra được sự đồng thời của bạn. Không chắc chắn điều này làm gì với bộ nhớ và spaghetti phụ thuộc vòng tròn mặc dù, bất cứ ai khác muốn hòa nhập? Tùy chọn khác là Message
/ Handler
nhưng bạn vẫn có thể có vấn đề tương tranh.
Trong dự án của tôi, tôi đã sử dụng AlertDialog.Builder
đã rất nhiều trước khi tôi phát hiện ra rằng đó là vấn đề. Tuy nhiên, tôi không muốn thay đổi nhiều mã ở bất kỳ đâu trong ứng dụng của mình. Ngoài ra, tôi thực sự là một fan hâm mộ của việc chuyển qua OnClickListeners
các lớp ẩn danh khi cần thiết (nghĩa là khi sử dụng setPositiveButton()
, setNegativeButton()
v.v.) thay vì phải thực hiện hàng ngàn phương thức gọi lại để giao tiếp giữa một đoạn hộp thoại và đoạn giữ, có thể, trong ý kiến của tôi, dẫn đến mã rất khó hiểu và phức tạp. Đặc biệt, nếu bạn có nhiều hộp thoại khác nhau trong một đoạn và sau đó cần phân biệt trong triển khai gọi lại giữa hộp thoại nào hiện đang được hiển thị.
Do đó, tôi đã kết hợp các cách tiếp cận khác nhau để tạo ra một AlertDialogFragment
lớp trợ giúp chung có thể được sử dụng chính xác như AlertDialog
:
GIẢI PHÁP
( XIN LƯU Ý rằng tôi đang sử dụng các biểu thức lambda Java 8 trong mã của mình, vì vậy bạn có thể phải thay đổi các phần của mã nếu bạn chưa sử dụng biểu thức 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
}
SỬ DỤNG
// 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();
Tôi đăng bài này ở đây không chỉ để chia sẻ giải pháp của mình, mà còn vì tôi muốn hỏi mọi người về ý kiến của bạn: Cách tiếp cận này hợp pháp hay có vấn đề ở một mức độ nào đó?
Tôi có thể đề nghị một chút đơn giản hóa câu trả lời của @ 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();
}
Nó loại bỏ sự cần thiết của người dùng (của lớp) để làm quen với các phần bên trong của thành phần và làm cho việc sử dụng thực sự đơn giản:
AlertDialogFragment.showAlert(title, message, this);
PS Trong trường hợp của tôi, tôi cần một hộp thoại cảnh báo đơn giản để đó là những gì tôi đã tạo. Bạn có thể áp dụng cách tiếp cận với Có / Không hoặc bất kỳ loại nào bạn cần.
Sử dụng Hộp thoại cho các hộp thoại đơn giản hoặc không.
Khi bạn cần các chế độ xem phức tạp hơn, trong đó bạn cần nắm giữ vòng đời như oncreate, yêu cầu quyền, bất kỳ ghi đè vòng đời nào tôi sẽ sử dụng một đoạn hộp thoại. Do đó, bạn tách các quyền và bất kỳ mã nào khác mà hộp thoại cần để hoạt động mà không phải giao tiếp với hoạt động gọi.
Dialog
hoặcAlertDialog.Builder::create()::show()
sẽ tạo một hộp thoại biến mất khi bạn xoay màn hình.