Відповіді:
Так, використовуйте DialogFragment
і в onCreateDialog
будь-якому випадку ви можете просто використовувати конструктор AlertDialog, щоб створити простий за AlertDialog
допомогою кнопок підтвердження Так / Ні. Не дуже багато коду взагалі.
Що стосується обробки подій у вашому фрагменті, то існують різні способи його виконання, але я просто визначаю повідомлення Handler
у своєму Fragment
, передаю його 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
яка буде нульовою, якщо ви завантажуєте її з пакета. Якщо ціль Повідомлення є нульовою, і ви використовуєте sendToTarget
, ви отримаєте NullPointerException - не тому, що Повідомлення є нульовим, а тому, що його метою є.
Ви можете створити загальні підкласи 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
- це спеціальний int, який є кодом запиту. getFragmentManager()
отримує менеджер фрагментів для активності та onActivityResult()
метод зворотного виклику фрагмента життєвого циклу.
З моменту впровадження API рівня 13 :
ShowDialog метод від діяльності є застарілим . Викликати діалог в інше місце в коді не рекомендується, оскільки вам доведеться самостійно керувати діалогом (наприклад, зміна орієнтації).
Різниця DialogFragment - AlertDialog
Чи так вони різні? З посилання на Android щодо DialogFragment :
ДіалогФрагмент - це фрагмент, який відображає діалогове вікно, що плаває вгорі вікна своєї діяльності. Цей фрагмент містить об'єкт "Діалог", який він відображає відповідно до стану фрагмента. Контроль діалогу (прийняття рішення , коли потрібно показати, приховати, відхилити його) має бути зроблено через API тут , а не прямим виклики на діалозі.
Інші примітки
Я б рекомендував використовувати DialogFragment
.
Звичайно, створення діалогового вікна "Так / Ні" досить складне, враховуючи, що це має бути досить простою задачею, але створення подібного діалогового вікна також Dialog
напрочуд складне.
(Життєвий цикл діяльності ускладнює його. Ви повинні дозволити Activity
керувати життєвим циклом діалогового вікна - і немає можливості передавати спеціальні параметри, наприклад, користувацьке повідомлення, Activity.showDialog
якщо використовуєте рівні API до 8)
Приємно те, що зазвичай ви можете побудувати власну абстракцію поверх DialogFragment
дуже легко.
String
параметр. Наприклад, коли користувач натискає "Так", діалогове вікно викликає метод діяльності з параметром "погодитися". Ці параметри задаються під час відображення діалогового вікна, наприклад AskDialog.ask ("Чи згодні ви з цими умовами?", "Згодні", "не згодні");
FragmentManager
«S findFragmentByTag
. Але так, це вимагає неабиякого коду.
Fragment
this
і мати Activity
extends
своє Interface
. Але обережно вкладаючи нитки, ви можете відмовлятись від інтерфейсних дзвінків, коли ви не обов'язково хочете їх, якщо ваша паралельність не перевірена. Не впевнений, що це стосується спагетті пам’яті та кругової залежності, чи хотів би хтось ще подзвонити? Інший варіант - Message
/ Handler
але у вас все ще можуть виникнути проблеми з одночасністю.
У моєму проекті, я вже використав AlertDialog.Builder
уже багато , перш ніж я дізнався, що це проблематично. Однак я не хотів змінювати стільки коду в будь-якому місці свого додатка. Крім того, я фактично прихильник проходження OnClickListeners
анонімних класів там, де вони потрібні (тобто під час використання setPositiveButton()
,setNegativeButton()
і т.д.) , замість того , щоб реалізувати тисячі методів зворотного виклику для обміну даних між діалоговим фрагментом і фрагментом власника, який може, в на мою думку, ведуть до дуже заплутаного та складного коду. Тим більше, якщо у вас є декілька різних діалогів в одному фрагменті, а потім потрібно розрізняти в реалізаціях зворотного виклику, між якими діалоговими вікнами в даний момент показано.
Тому я поєднав різні підходи, щоб створити загальний AlertDialogFragment
клас помічників, який можна використовувати саме так AlertDialog
:
РІШЕННЯ
( ВВАЖАЙТЕ, що я використовую лямбда-вирази Java 8 у своєму коді, тому вам, можливо, доведеться змінити частини коду, якщо ви ще не використовуєте лямбда-вирази .)
/**
* 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);
PS У моєму випадку мені знадобився простий діалог попередження, щоб я створив це. Ви можете застосувати підхід до так / ні або будь-якого іншого типу, який вам потрібен.
Використовуйте діалогове вікно для простих діалогів так чи ні.
Коли вам потрібні більш складні представлення даних, у яких вам потрібно впоратися з життєвим циклом, таким як oncreate, запити дозволів, будь-яке переосмислення життєвого циклу, я б використав фрагмент діалогу. Таким чином, ви відокремлюєте дозволи та будь-який інший код, у якому потрібно діяти діалоговому вікні, не потребуючи спілкування з викликом.
Dialog
абоAlertDialog.Builder::create()::show()
створить діалогове вікно, яке зникає при повороті екрана.