Comment implémenter une vue AlertDialog personnalisée

107

Dans la documentation Android sur AlertDialog , il donne les instructions et l'exemple suivants pour définir une vue personnalisée dans un AlertDialog:

Si vous souhaitez afficher une vue plus complexe, recherchez le FrameLayout appelé "body" et ajoutez-y votre vue:

FrameLayout fl = (FrameLayout) findViewById(R.id.body);
fl.add(myView, new LayoutParams(FILL_PARENT, WRAP_CONTENT));

Tout d'abord, il est assez évident que add()c'est une faute de frappe et qu'elle est censée l'être addView().

Je suis confus par la première ligne utilisant R.id.body. Il semble que ce soit l'élément body de AlertDialog ... mais je ne peux pas simplement entrer cela dans mon code car cela donne une erreur de compilation. Où R.id.body est-il défini ou attribué ou autre?

Voici mon code. J'ai essayé d'utiliser setView(findViewById(R.layout.whatever)sur le constructeur mais cela n'a pas fonctionné. Je suppose que je ne l'ai pas gonflé manuellement?

AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle("Title")
    .setCancelable(false)
    .setPositiveButton("Go", new DialogInterface.OnClickListener() {

    @Override
    public void onClick(DialogInterface dialog, int id) {
        EditText textBox = (EditText) findViewById(R.id.textbox);
        doStuff();
    }
});

FrameLayout f1 = (FrameLayout)findViewById(R.id.body /*CURRENTLY an ERROR*/);
f1.addView(findViewById(R.layout.dialog_view));

AlertDialog alert = builder.create();
alert.show();
stormin986
la source
Pour rechercher et utiliser vos objets dans une boîte de dialogue, suivez ces quatre étapes: stackoverflow.com/a/18773261/1699586
Sara
3
Réponse en une ligne: ajoutez .setView(getLayoutInflater().inflate(R.layout.dialog_view, null))au constructeur. Crédit à Sergio Viudes, ci-dessous.
1 ''

Réponses:

49

Vous avez raison, c'est parce que vous ne l'avez pas gonflé manuellement. Il semble que vous essayez «d'extraire» l'identifiant «corps» de la mise en page de votre activité, et cela ne fonctionnera pas.

Vous voulez probablement quelque chose comme ça:

LayoutInflater inflater = getLayoutInflater();
FrameLayout f1 = (FrameLayout)alert.findViewById(android.R.id.body);
f1.addView(inflater.inflate(R.layout.dialog_view, f1, false));
synique
la source
17
Il est intéressant de noter que le corps n'est pas défini comme une constante dans android.R.id. Je ne suis toujours pas clair sur la façon d'accéder à l'élément «body» du AlertDialog créé. J'aimerais toujours savoir comment faire cela, mais pour l'instant, je vais simplement essayer de gonfler une vue et d'utiliser setView dans le générateur.
stormin986
2
En fait, cela me laisse encore une question alors (je suis nouveau pour gonfler les vues). En utilisant builder.setView(inflater.inflate(R.id.dialog, ROOT_VIEWGROUP[, ATTACH_TO_ROOT])), la documentation indique que le groupe de vues racine est facultatif. Doit-il être utilisé dans ce cas? Si tel est le cas ... vous devez encore trouver comment obtenir une référence à AlertDialog ...
stormin986
2
C'est facultatif, mais vous n'aurez pas de référence au parent de l'intérieur de la mise en page que vous gonflez. Des choses comme android: layout_gravity ne fonctionneront pas sur la vue de niveau supérieur ... et peut-être que vous n'en avez pas besoin. Lorsque vous appelez AlertDialog alert = builder.create (), vous avez une référence à votre AlertDialog. Réponse longue courte, elle est facultative. Essayez-le, en fonction de ce que vous faites dans votre mise en page personnalisée, cela fonctionnera probablement.
synic
2
Je ne suis pas sûr de savoir comment référencer la vue dans AlertDialog. Que recommanderiez-vous dans ce cas si je voulais faire référence au parent? La seule chose que je vois dans alertDialog qui renvoie une vue est getCurrentFocus ()
stormin986
5
Tenez le Viewvous gonflez. Faites appel findViewById()à cela Viewlorsque vous avez besoin d'éléments de son contenu. Voir: github.com/commonsguy/cw-android/tree/master/Database/Constants
CommonsWare
159

Vous pouvez créer votre vue directement à partir du Layout Inflater, il vous suffit d'utiliser le nom de votre fichier XML de mise en page et l'ID de la mise en page dans le fichier.

Votre fichier XML doit avoir un identifiant comme celui-ci:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/dialog_layout_root"
              android:orientation="vertical"
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:padding="10dp"
              />

Et puis vous pouvez définir votre mise en page sur le générateur avec le code suivant:

LayoutInflater inflater = getLayoutInflater();
View dialoglayout = inflater.inflate(R.layout.dialog_layout, null);
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setView(dialoglayout);
builder.show();
Andrewx2
la source
4
Et où est R.id.dialog_layout_root dans cet exemple? N'est-ce pas une vue dans l'activité actuelle?
Alex Pretzlav
5
@AlexPretzlav: dialog_layout_root n'est pas nécessaire dans cet exemple. Tout ce dont vous avez besoin est le nom de votre fichier xml pour R.layout. [Nom_of_xml_file].
IgorGanapolsky
7
@Temperage Avez-vous ajouté builder.show à la fin. J'ai essayé ce code et cela a fonctionné. Aussi j'ai passé null comme deuxième paramètre pour infalter.inflate
Vinoth
2
Cela aurait dû être sélectionné comme la meilleure réponse.
Salman Khakwani
2
Cela donne une ClassCastException lorsque la disposition personnalisée contient un EditText, car getCurrentFocus()retournera le EditText et un EditText ne peut pas être casté en ViewGroup. L'utilisation nullcomme deuxième argument résout ce problème.
1 ''
20

android.R.id.custom renvoyait null pour moi. J'ai réussi à faire fonctionner cela au cas où quelqu'un rencontrerait le même problème,

AlertDialog.Builder builder = new AlertDialog.Builder(context)
            .setTitle("My title")
            .setMessage("Enter password");
final FrameLayout frameView = new FrameLayout(context);
builder.setView(frameView);

final AlertDialog alertDialog = builder.create();
LayoutInflater inflater = alertDialog.getLayoutInflater();
View dialoglayout = inflater.inflate(R.layout.simple_password, frameView);
alertDialog.show();

Pour référence, R.layout.simple_password est:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
          android:orientation="vertical"
          android:layout_width="match_parent"
          android:layout_height="match_parent">

<EditText
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:id="@+id/password_edit_view"
        android:inputType="textPassword"/>
<CheckBox
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/show_password"
        android:id="@+id/show_password_checkbox"
        android:layout_gravity="left|center_vertical"
        android:checked="false"/>

</LinearLayout>
John Rellis
la source
John Rellis. Votre solution fonctionne bien. Ayez juste quelque chose dedans. Nous devrions gonfler avant builder.create et définir frameView.setLayoutParams (new LayoutParams (LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)); ou quelque chose d'abord, qui évitera certains bugs
inattendus
merci pour les commentaires .. comment gonflez-vous avant builder.create ()? inflate est appelé sur le gonfleur de la boîte de dialogue et je n'ai qu'une boîte de dialogue parce que j'ai appelé builder.create ()
John Rellis
nous pouvons utiliser le gonfleur d'activité et ne pas nous attacher à FrameLayout, nous pouvons donc réduire un peu de code comme ceci.AlertDialog.Builder builder = new AlertDialog.Builder (nouveau ContextThemeWrapper (getActivity (), android.R.style.Theme_Holo)) .setTitle ("titre") .setIcon (R.drawable.sample_icon); Afficher troubleView = inflater.inflate (R.layout.sample_layout, null, false); builder.setView (troubleView); alert = constructeur.create (); Désolé, je ne sais pas comment écrire clairement le code sur un commentaire
MOBYTELAB
C'est juste une question de bogue de conception, si nous rassemblons tout dans la mise en page xml et oublions Framelayout comme racine de dialogue, nous pouvons être curieux de savoir si la mise en page en xml n'est pas un parent de remplissage ou quelque chose, juste un cas.
MOBYTELAB
A travaillé pour moi, merci! Celui-ci me permet d'ajouter un titre et mon bouton Annuler et OK, y compris le EditText sans erreur. :) La seule question est, comment ça marche? Agit-il FrameLayoutcomme une sorte de fragment? Lorsque j'ai essayé la réponse d'Andrewx2 ci-dessus, j'ai eu une erreur car il pensait que je gonflais deux mises en page (c'est mon estimation).
Azurespot
12

Cela a fonctionné pour moi:

dialog.setView(dialog.getLayoutInflater().inflate(R.layout.custom_dialog_layout, null));
Sergio Viudes
la source
8

AlertDialog personnalisé

Cet exemple complet inclut le renvoi de données à l'activité.

entrez la description de l'image ici

Créer une mise en page personnalisée

Une mise en page avec un EditTextest utilisée pour cet exemple simple, mais vous pouvez la remplacer par tout ce que vous voulez.

custom_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:orientation="vertical"
              android:paddingLeft="20dp"
              android:paddingRight="20dp"
              android:layout_width="match_parent"
              android:layout_height="match_parent">

    <EditText
        android:id="@+id/editText"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>

</LinearLayout>

Utiliser la boîte de dialogue dans le code

Les éléments clés sont

  • en utilisant setViewpour attribuer la mise en page personnalisée auAlertDialog.Builder
  • renvoyer toutes les données à l'activité lorsqu'un bouton de dialogue est cliqué.

Ceci est le code complet de l'exemple de projet montré dans l'image ci-dessus:

MainActivity.java

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void showAlertDialogButtonClicked(View view) {

        // create an alert builder
        AlertDialog.Builder builder = new AlertDialog.Builder(this);
        builder.setTitle("Name");

        // set the custom layout
        final View customLayout = getLayoutInflater().inflate(R.layout.custom_layout, null);
        builder.setView(customLayout);

        // add a button
        builder.setPositiveButton("OK", new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                // send data from the AlertDialog to the Activity
                EditText editText = customLayout.findViewById(R.id.editText);
                sendDialogDataToActivity(editText.getText().toString());
            }
        });

        // create and show the alert dialog
        AlertDialog dialog = builder.create();
        dialog.show();
    }

    // do something with the data coming from the AlertDialog
    private void sendDialogDataToActivity(String data) {
        Toast.makeText(this, data, Toast.LENGTH_SHORT).show();
    }
}

Remarques

  • Si vous vous retrouvez à utiliser ceci à plusieurs endroits, envisagez de créer une DialogFragmentsous - classe comme décrit dans la documentation .

Voir également

Suragch
la source
1
EditText editText = customLayout.findViewById(R.id.editText); devrait être EditText editText = (EditText) customLayout.findViewById(R.id.editText);
philcruz
4
@philcruz, si vous passez à Android Studio 3.0, vous n'avez plus à diffuser explicitement la vue. L'EDI peut déduire le type. C'est une fonctionnalité très intéressante. Cela aide beaucoup à nettoyer le code.
Suragch
excellente réponse, vraiment utile
Noor Hossain
4

Les lignes de code les plus simples qui fonctionnent pour moi sont les suivantes:

AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setView(R.layout.layout_resource_id);
builder.show();

Quel que soit le type de mise en page (LinearLayout, FrameLayout, RelativeLayout) fonctionnera setView et différera simplement dans l'apparence et le comportement.

Kenix
la source
génial, simple et facile
Frank Eno
2

Le moyen le plus simple de le faire est d'utiliser android.support.v7.app.AlertDialogau lieu de android.app.AlertDialogwhere public AlertDialog.Builder setView (int layoutResId)can be used below API 21.

new AlertDialog.Builder(getActivity())
    .setTitle(title)
    .setView(R.layout.dialog_basic)
    .setPositiveButton(android.R.string.ok,
        new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton) {
                //Do something
            }
        }
    )
    .setNegativeButton(android.R.string.cancel,
        new DialogInterface.OnClickListener() {
            public void onClick(DialogInterface dialog, int whichButton) {
                //Do something
            }
        }
    )
    .create();
flanaras
la source
Quiconque lit ceci après l'introduction de l'API 21 devrait utiliser cette méthode. Comme l'indique la réponse, si votre application minSDKversion est inférieure à 21, utilisez le package AlerDialog du support. Voila !!!
Dibzmania
2

AlertDialog.setView(View view)ajoute la vue donnée au fichier R.id.custom FrameLayout. Ce qui suit est un extrait de code source Android à partir AlertController.setupView()duquel finalement gère cela ( mViewest la vue donnée à la AlertDialog.setViewméthode).

...
FrameLayout custom = (FrameLayout) mWindow.findViewById(R.id.**custom**);
custom.addView(**mView**, new LayoutParams(FILL_PARENT, FILL_PARENT));
...
Jung Soo Kim
la source
1

Après avoir changé l'ID android.R.id.custom, j'ai dû ajouter ce qui suit pour que la vue s'affiche:

((View) f1.getParent()).setVisibility(View.VISIBLE);

Cependant, cela a provoqué le rendu de la nouvelle vue dans une grande vue parent sans arrière-plan, ce qui a divisé la boîte de dialogue en deux parties (texte et boutons, avec la nouvelle vue entre les deux). J'ai enfin obtenu l'effet que je voulais en insérant ma vue à côté du message:

LinearLayout f1 = (LinearLayout)findViewById(android.R.id.message).getParent().getParent();

J'ai trouvé cette solution en explorant l'arborescence des vues avec View.getParent () et View.getChildAt (int). Pas vraiment content non plus. Rien de tout cela ne se trouve dans les documents Android et s'ils modifient un jour la structure de AlertDialog, cela pourrait casser.

Mantha noir
la source
1

Il serait plus logique de le faire de cette façon, avec le moins de code possible.

new AlertDialog.Builder(this).builder(this)
        .setTitle("Title")
        .setView(R.id.dialog_view)   //notice this setView was added
        .setCancelable(false)
        .setPositiveButton("Go", new DialogInterface.OnClickListener() {
            @Override 
            public void onClick(DialogInterface dialog, int id) {
                EditText textBox = (EditText) findViewById(R.id.textbox);
                doStuff();
            }
        }).show();

Pour obtenir une liste étendue des éléments que vous pouvez définir, commencez à taper .setdans Android Studio

StackAttack
la source