Réponses:
Oui, utilisez DialogFragment
et onCreateDialog
vous pouvez simplement utiliser un générateur AlertDialog pour créer un simple AlertDialog
avec des boutons de confirmation Oui / Non. Pas beaucoup de code du tout.
En ce qui concerne la gestion des événements dans votre fragment, il y aurait différentes façons de le faire, mais je définis simplement un message Handler
dans my Fragment
, le transmets au DialogFragment
via son constructeur, puis je retransmets les messages au gestionnaire de mon fragment en fonction des différents événements de clic. Encore une fois, différentes façons de le faire, mais ce qui suit fonctionne pour moi.
Dans la boîte de dialogue, maintenez un message et instanciez-le dans le constructeur:
private Message okMessage;
...
okMessage = handler.obtainMessage(MY_MSG_WHAT, MY_MSG_OK);
Implémentez le onClickListener
dans votre boîte de dialogue, puis appelez le gestionnaire selon le cas:
public void onClick(.....
if (which == DialogInterface.BUTTON_POSITIVE) {
final Message toSend = Message.obtain(okMessage);
toSend.sendToTarget();
}
}
Éditer
Et comme il Message
est parcellable, vous pouvez l'enregistrer onSaveInstanceState
et le restaurer
outState.putParcelable("okMessage", okMessage);
Puis dans onCreate
if (savedInstanceState != null) {
okMessage = savedInstanceState.getParcelable("okMessage");
}
target
qui sera nul si vous le chargez depuis un bundle. Si la cible d'un Message est nulle et que vous l'utilisez sendToTarget
, vous obtiendrez une NullPointerException - non pas parce que le Message est nul, mais parce que sa cible l'est.
Vous pouvez créer des sous-classes DialogFragment génériques comme YesNoDialog et OkDialog, et transmettre le titre et le message si vous utilisez beaucoup de boîtes de dialogue dans votre application.
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();
}
}
Appelez-le ensuite comme suit:
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");
Et gérez le résultat onActivityResult
.
YES_NO_CALL
, getFragmentManager()
et onActivityResult
?
YES_NO_CALL
est un int personnalisé qui est le code de demande. getFragmentManager()
obtient le gestionnaire de fragments pour l'activité et onActivityResult()
est une méthode de rappel de cycle de vie de fragment.
Depuis l'introduction de l'API niveau 13 :
la méthode showDialog de Activity est déconseillée . Il n'est pas conseillé d'invoquer une boîte de dialogue ailleurs dans le code car vous devrez gérer la boîte de dialogue vous-même (par exemple, changement d'orientation).
Dialogue de différence - Fragment - AlertDialog
Sont-ils tellement différents? De la référence Android concernant DialogFragment :
Un DialogFragment est un fragment qui affiche une fenêtre de dialogue, flottant au-dessus de la fenêtre de son activité. Ce fragment contient un objet Dialog, qu'il affiche comme approprié en fonction de l'état du fragment. Le contrôle de la boîte de dialogue (décider quand l'afficher, la masquer, la fermer) doit être fait via l'API ici , pas avec des appels directs sur la boîte de dialogue.
Autres notes
Je recommanderais d'utiliser DialogFragment
.
Bien sûr, la création d'une boîte de dialogue "Oui / Non" avec elle est assez complexe étant donné que cela devrait être une tâche plutôt simple, mais la création d'une boîte de dialogue similaire avec Dialog
est également étonnamment compliquée.
(Le cycle de vie des activités complique les choses - vous devez laisser Activity
gérer le cycle de vie de la boîte de dialogue - et il n'y a aucun moyen de transmettre des paramètres personnalisés, par exemple le message personnalisé, Activity.showDialog
si vous utilisez des niveaux d'API inférieurs à 8)
La bonne chose est que vous pouvez généralement construire votre propre abstraction par dessus DialogFragment
assez facilement.
String
paramètre. Lorsque l'utilisateur clique sur "Oui", par exemple, la boîte de dialogue appelle la méthode de l'activité avec le paramètre "d'accord". Ces paramètres sont spécifiés lors de l'affichage de la boîte de dialogue, par exemple AskDialog.ask ("Êtes-vous d'accord avec ces termes?", "D'accord", "en désaccord");
FragmentManager
's findFragmentByTag
. Mais oui, cela nécessite un peu de code.
Fragment
this
et avoir votre Activity
extends
votre Interface
. Attention au threading cependant, vous pouvez sauter des appels d'interface lorsque vous n'en voulez pas nécessairement si votre concurrence n'est pas en échec. Vous n'êtes pas sûr de ce que cela fait avec la mémoire et les spaghettis de dépendance circulaire, est-ce que quelqu'un d'autre aimerait jouer? L'autre option est Message
/ Handler
mais vous pouvez toujours rencontrer des problèmes de concurrence.
Dans mon projet, j'en utilisais AlertDialog.Builder
déjà déjà beaucoup avant de découvrir que c'était problématique. Cependant, je ne voulais pas changer autant de code n'importe où dans mon application. De plus, je suis en fait un fan de passer OnClickListeners
en classes anonymes là où elles sont nécessaires (c'est-à-dire lors de l'utilisation setPositiveButton()
, setNegativeButton()
etc.) au lieu d'avoir à implémenter des milliers de méthodes de rappel pour communiquer entre un fragment de dialogue et le fragment de support, ce qui peut, dans à mon avis, conduire à un code très déroutant et complexe. Surtout, si vous avez plusieurs boîtes de dialogue différentes dans un fragment et que vous devez ensuite distinguer dans les implémentations de rappel quelle boîte de dialogue est actuellement affichée.
Par conséquent, j'ai combiné différentes approches pour créer une AlertDialogFragment
classe d'assistance générique qui peut être utilisée exactement comme AlertDialog
:
SOLUTION
( VEUILLEZ NOTER que j'utilise des expressions lambda Java 8 dans mon code, vous devrez donc peut-être modifier des parties du code si vous n'utilisez pas encore d' expressions 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
}
USAGE
// 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();
Je poste ceci ici non seulement pour partager ma solution, mais aussi parce que je voulais vous demander votre opinion: cette approche est-elle légitime ou problématique dans une certaine mesure?
Puis-je suggérer une petite simplification de la réponse de @ 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();
}
Il supprime la nécessité pour l'utilisateur (de la classe) de se familiariser avec les composants internes du composant et rend l'utilisation très simple:
AlertDialogFragment.showAlert(title, message, this);
PS Dans mon cas, j'avais besoin d'une simple boîte de dialogue d'alerte, c'est donc ce que j'ai créé. Vous pouvez appliquer l'approche à un type Oui / Non ou à tout autre type dont vous avez besoin.
Utilisez Dialogue pour des dialogues simples oui ou non.
Lorsque vous avez besoin de vues plus complexes dans lesquelles vous devez vous procurer le cycle de vie, comme oncreate, demander des autorisations, tout remplacement de cycle de vie, j'utiliserais un fragment de dialogue. Ainsi, vous séparez les autorisations et tout autre code dont la boîte de dialogue a besoin pour fonctionner sans avoir à communiquer avec l'activité d'appel.
Dialog
ouAlertDialog.Builder::create()::show()
créera une boîte de dialogue qui disparaît lorsque vous faites pivoter l'écran.