Risposte:
Sì, usa DialogFragment
e onCreateDialog
puoi semplicemente usare un builder AlertDialog per creare un sempliceAlertDialog
con i pulsanti di conferma Sì / No. Non c'è molto codice.
Per quanto riguarda la gestione degli eventi nel tuo frammento, ci sarebbero vari modi per farlo, ma io definisco semplicemente un messaggio Handler
nel mio Fragment
, lo passo nellaDialogFragment
suo costruttore e poi rispondo i messaggi al gestore del mio frammento come appropriato sui vari eventi di clic. Ancora una volta vari modi per farlo, ma quanto segue funziona per me.
Nella finestra di dialogo tieni un messaggio e creane un'istanza nel costruttore:
private Message okMessage;
...
okMessage = handler.obtainMessage(MY_MSG_WHAT, MY_MSG_OK);
Implementare onClickListener
nella finestra di dialogo e quindi chiamare il gestore come appropriato:
public void onClick(.....
if (which == DialogInterface.BUTTON_POSITIVE) {
final Message toSend = Message.obtain(okMessage);
toSend.sendToTarget();
}
}
modificare
E come Message
è parcelable puoi salvarlo onSaveInstanceState
e ripristinarlo
outState.putParcelable("okMessage", okMessage);
Quindi dentro onCreate
if (savedInstanceState != null) {
okMessage = savedInstanceState.getParcelable("okMessage");
}
target
che sarà nullo se lo si carica da un pacchetto. Se la destinazione di un messaggio è nulla e si utilizza sendToTarget
, si otterrà una NullPointerException, non perché il messaggio è nullo, ma perché la sua destinazione lo è.
Puoi creare sottoclassi generiche di DialogFragment come YesNoDialog e OkDialog e passare il titolo e il messaggio se usi molto le finestre di dialogo nella tua app.
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();
}
}
Quindi chiamalo usando il seguente:
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");
E gestisci il risultato onActivityResult
.
YES_NO_CALL
, getFragmentManager()
e onActivityResult
?
YES_NO_CALL
è un int personalizzato che è il codice di richiesta. getFragmentManager()
ottiene il gestore frammento per l'attività ed onActivityResult()
è un metodo di callback del ciclo di vita del frammento.
Dall'introduzione del livello API 13 :
il metodo showDialog da Activity è obsoleto . Non è consigliabile invocare una finestra di dialogo altrove nel codice poiché sarà necessario gestire la finestra di dialogo da soli (ad es. Cambio di orientamento).
Difference DialogFragment - AlertDialog
Sono così diversi? Da riferimento Android per quanto riguarda DialogFragment :
Un DialogFragment è un frammento che visualizza una finestra di dialogo, che galleggia sopra la finestra della sua attività. Questo frammento contiene un oggetto Finestra di dialogo, che viene visualizzato come appropriato in base allo stato del frammento. Il controllo della finestra di dialogo (per decidere quando mostrare, nascondere, ignorarla) dovrebbe essere effettuato tramite l'API qui , non con chiamate dirette nella finestra di dialogo.
Altre note
Consiglierei di usare DialogFragment
.
Certo, la creazione di una finestra di dialogo "Sì / No" è piuttosto complessa considerando che dovrebbe essere un'attività piuttosto semplice, ma creare una finestra di dialogo simile con Dialog
è sorprendentemente complicato.
(Il ciclo di vita delle attività lo rende complicato, è necessario consentire la Activity
gestione del ciclo di vita della finestra di dialogo) e non è possibile passare parametri personalizzati, ad esempio il messaggio personalizzato aActivity.showDialog
se si utilizzano livelli API inferiori a 8
La cosa bella è che di solito puoi costruire la tua astrazione su DialogFragment
abbastanza facilmente.
String
parametro. Quando l'utente fa clic su "Sì", ad esempio, la finestra di dialogo chiama il metodo Activity con il parametro "accetta". Questi parametri vengono specificati quando si visualizza la finestra di dialogo, ad esempio AskDialog.ask ("Accetti questi termini?", "Acconsenti", "non sono d'accordo");
FragmentManager
's findFragmentByTag
. Ma sì, richiede un bel po 'di codice.
Fragment
this
e avere il Activity
extends
tuo Interface
. Attento al threading, tuttavia, potresti interrompere le chiamate di interfaccia quando non le desideri necessariamente se la tua concorrenza non è sotto controllo. Non sei sicuro di cosa faccia questo con gli spaghetti con memoria e dipendenza circolare, qualcun altro vorrebbe entrare? L'altra opzione è Message
/ Handler
ma potresti avere ancora problemi di concorrenza.
Nel mio progetto, ho già usato AlertDialog.Builder
già molto prima che ho scoperto che è problematico. Tuttavia, non volevo cambiare quel codice in nessun punto della mia app. Inoltre, in realtà sono un fan del passaggio OnClickListeners
come classi anonime dove sono necessarie (ovvero, quando si utilizza setPositiveButton()
,setNegativeButton()
ecc.) Invece di dover implementare migliaia di metodi di callback per comunicare tra un frammento di dialogo e il frammento del titolare, che può, in la mia opinione porta a un codice molto confuso e complesso. In particolare, se si hanno più finestre di dialogo diverse in un frammento e quindi è necessario distinguere nelle implementazioni di callback tra le finestre di dialogo attualmente visualizzate.
Pertanto, ho combinato diversi approcci per creare una AlertDialogFragment
classe helper generica che può essere utilizzata esattamente come AlertDialog
:
SOLUZIONE
( SI PREGA DI NOTARE che sto usando espressioni lambda Java 8 nel mio codice, quindi potresti dover cambiare parti del codice se non usi ancora espressioni 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
}
USO
// 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();
Sto pubblicando questo qui non solo per condividere la mia soluzione, ma anche perché volevo chiedervi la vostra opinione: questo approccio è in qualche modo legittimo o problematico?
Vorrei suggerire una piccola semplificazione della risposta di @ 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();
}
Elimina la necessità che l'utente (della classe) abbia familiarità con gli interni del componente e rende l'utilizzo molto semplice:
AlertDialogFragment.showAlert(title, message, this);
PS Nel mio caso avevo bisogno di una semplice finestra di avviso, quindi è quello che ho creato. È possibile applicare l'approccio a un Sì / No o qualsiasi altro tipo necessario.
Utilizzare la finestra di dialogo per semplici finestre di dialogo Sì o No.
Quando sono necessarie viste più complesse in cui è necessario acquisire il ciclo di vita, ad esempio oncreate, richiedere autorizzazioni, qualsiasi sostituzione del ciclo di vita, utilizzare un frammento di finestra di dialogo. In questo modo si separano le autorizzazioni e qualsiasi altro codice che la finestra di dialogo deve funzionare senza dover comunicare con l'attività chiamante.
Dialog
oAlertDialog.Builder::create()::show()
creerà una finestra di dialogo che scompare quando si ruota lo schermo.