En quoi l'attribut XML android: onClick diffère-t-il exactement de setOnClickListener?

416

D'après ce que j'ai lu, vous pouvez affecter un onClickgestionnaire à un bouton de deux manières.

En utilisant l' android:onClickattribut XML où vous utilisez simplement le nom d'une méthode publique avec la signature void name(View v)ou en utilisant la setOnClickListenerméthode où vous passez un objet qui implémente l' OnClickListenerinterface. Ce dernier nécessite souvent une classe anonyme que personnellement je n'aime pas (goût personnel) ou définissant une classe interne qui implémente le OnClickListener.

En utilisant l'attribut XML, vous avez juste besoin de définir une méthode au lieu d'une classe, donc je me demandais si la même chose pouvait être faite via du code et non dans la disposition XML.

emitrax
la source
4
J'ai lu votre problème et je pense que vous êtes coincé au même endroit que moi. Je suis tombé sur une très bonne vidéo qui m'a beaucoup aidé à résoudre mon problème. Retrouvez la vidéo sur le lien suivant: youtube.com/watch?v=MtmHURWKCmg&feature=youtu.be J'espère que cela vous aidera aussi :)
user2166292
9
Pour ceux qui veulent gagner du temps en regardant la vidéo publiée dans le commentaire ci-dessus, cela montre simplement comment deux boutons peuvent avoir la même méthode pour son onClickattribut dans le fichier de mise en page. Cela se fait grâce au paramètre View v. Vous vérifiez simplement if (v == findViewById(R.id.button1)) etc.
CodyBugstein
13
@Imray Je pense qu'il vaut mieux l'utiliser v.getId() == R.id.button1, car vous n'avez pas besoin de trouver le contrôle réel et de faire une comparaison. Et vous pouvez utiliser un switchau lieu de beaucoup d'if.
Sami Kuhmonen
3
Ce tutoriel vous aidera beaucoup. cliquez ici
c49
Utiliser xml android: onClick provoque un crash.

Réponses:

604

Non, ce n'est pas possible via le code. Android implémente simplement le OnClickListenerpour vous lorsque vous définissez l' android:onClick="someMethod"attribut.

Ces deux extraits de code sont égaux, juste implémentés de deux manières différentes.

Implémentation du code

Button btn = (Button) findViewById(R.id.mybutton);

btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        myFancyMethod(v);
    }
});

// some more code

public void myFancyMethod(View v) {
    // does something very interesting
}

Ci-dessus est une implémentation de code d'un OnClickListener. Et ceci est l'implémentation XML.

Implémentation XML

<?xml version="1.0" encoding="utf-8"?>
<!-- layout elements -->
<Button android:id="@+id/mybutton"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Click me!"
    android:onClick="myFancyMethod" />
<!-- even more layout elements -->

En arrière-plan, Android ne fait rien d'autre que le code Java, appelant votre méthode lors d'un événement de clic.

Notez qu'avec le XML ci-dessus, Android ne recherchera la onClickméthode myFancyMethod()que dans l'activité en cours. Il est important de se rappeler si vous utilisez des fragments, car même si vous ajoutez le XML ci-dessus à l'aide d'un fragment, Android ne recherchera pas la onClickméthode dans le.java fichier du fragment utilisé pour ajouter le XML.

Une autre chose importante que j'ai remarquée. Vous avez mentionné que vous ne préférez pas les méthodes anonymes . Vous vouliez dire que vous n'aimez pas les cours anonymes .

Octavian A. Damiean
la source
4
Je ne suis pas un gourou de Java mais oui, je voulais dire une classe anonyme. Merci pour votre réponse. Très clair.
emitrax
118
Notez que si vous utilisez le XML onclick, vous devez mettre la méthode onclick ( myFancyMethod()) dans l'activité en cours. Ceci est important si vous utilisez des fragments, car la manière programmatique de définir les écouteurs onclick aura probablement la méthode de gestion des clics dans onCreateView () d'un fragment ... où il ne serait pas trouvé s'il était référencé à partir de XML.
Peter Ajtai
12
Oui, la méthode doit être publique.
Octavian A. Damiean
12
Ce qui est intéressant, c'est que le faire dans le code permet de protéger l'accès à la méthode en rendant la méthode privée, tandis que le faire de la manière xml conduit à l'exposition de la méthode.
bgse
5
Le fait que la fonction (dans l'approche XML) doit être dans Activity est important non seulement lorsque vous considérez des fragments, mais également des vues personnalisées (contenant le bouton). Lorsque vous avez une vue personnalisée que vous réutilisez dans plusieurs activités, mais que vous souhaitez utiliser la même méthode onClick pour tous les cas, la méthode XML n'est pas la plus pratique. Vous devez mettre ceci surClickMethod (avec le même corps) dans chaque activité qui utilise votre vue personnalisée.
Bartek Lipinski
87

Quand j'ai vu la première réponse, cela m'a fait réaliser que mon problème n'était pas de mettre le paramètre (View v) sur la méthode fantaisie:

public void myFancyMethod(View v) {}

Lorsque vous essayez d'y accéder à partir du xml, il faut utiliser

android:onClick="myFancyMethod"/>

J'espère que cela aide quelqu'un.

jp093121
la source
74

android:onClick est pour l'API de niveau 4 et plus, donc si vous ciblez <1,6, vous ne pouvez pas l'utiliser.

James
la source
33

Vérifiez si vous avez oublié de rendre la méthode publique!

Ruivo
la source
1
pourquoi est-il obligatoire de le rendre public?
eRaisedToX
3
@eRaisedToX Je pense que c'est assez clair: s'il n'est pas public, il ne peut pas être appelé depuis le framework Android.
m0skit0
28

Spécification des android:onClickrésultats d'attribut lors de l' Buttonappel d'instancesetOnClickListener interne. Il n'y a donc absolument aucune différence.

Pour bien comprendre, voyons comment XML onClick attribut est géré par le framework.

Lorsqu'un fichier de disposition est gonflé, toutes les vues qui y sont spécifiées sont instanciées. Dans ce cas spécifique, l' Buttoninstance est créée à l'aide du public Button (Context context, AttributeSet attrs, int defStyle)constructeur. Tous les attributs de la balise XML sont lus à partir du bundle de ressources et transmis AttributeSetau constructeur.

ButtonLa classe est héritée de la Viewclasse qui entraîne l' Viewappel du constructeur, qui prend en charge la définition du gestionnaire de rappel de clic via setOnClickListener.

L'attribut onClick défini dans attrs.xml est appelé dans View.java R.styleable.View_onClick.

Voici le code View.javaqui fait la plupart du travail pour vous en appelant setOnClickListenerpar lui-même.

 case R.styleable.View_onClick:
            if (context.isRestricted()) {
                throw new IllegalStateException("The android:onClick attribute cannot "
                        + "be used within a restricted context");
            }

            final String handlerName = a.getString(attr);
            if (handlerName != null) {
                setOnClickListener(new OnClickListener() {
                    private Method mHandler;

                    public void onClick(View v) {
                        if (mHandler == null) {
                            try {
                                mHandler = getContext().getClass().getMethod(handlerName,
                                        View.class);
                            } catch (NoSuchMethodException e) {
                                int id = getId();
                                String idText = id == NO_ID ? "" : " with id '"
                                        + getContext().getResources().getResourceEntryName(
                                            id) + "'";
                                throw new IllegalStateException("Could not find a method " +
                                        handlerName + "(View) in the activity "
                                        + getContext().getClass() + " for onClick handler"
                                        + " on view " + View.this.getClass() + idText, e);
                            }
                        }

                        try {
                            mHandler.invoke(getContext(), View.this);
                        } catch (IllegalAccessException e) {
                            throw new IllegalStateException("Could not execute non "
                                    + "public method of the activity", e);
                        } catch (InvocationTargetException e) {
                            throw new IllegalStateException("Could not execute "
                                    + "method of the activity", e);
                        }
                    }
                });
            }
            break;

Comme vous pouvez le voir, setOnClickListenerest appelé pour enregistrer le rappel, comme nous le faisons dans notre code. La seule différence est qu'il utiliseJava Reflection pour appeler la méthode de rappel définie dans notre activité.

Voici la raison des problèmes mentionnés dans d'autres réponses:

  • La méthode de rappel doit être publique : depuis qu'elle Java Class getMethodest utilisée, seules les fonctions avec spécificateur d'accès public sont recherchées. Sinon, soyez prêt à gérer l' IllegalAccessExceptionexception.
  • Lors de l'utilisation de Button avec onClick dans Fragment, le rappel doit être défini dans Activity : getContext().getClass().getMethod()call restreint la recherche de méthode au contexte actuel, qui est Activity en cas de Fragment. Par conséquent, la méthode est recherchée dans la classe Activity et non dans la classe Fragment.
  • La méthode de rappel doit accepter le paramètre View : puisque Java Class getMethodrecherche la méthode qui accepte View.classcomme paramètre.
Manish Mulimani
la source
1
C'était la pièce manquante pour moi - Java utilise Reflection pour trouver le gestionnaire de clics commençant par getContext (). C'était un peu mystérieux pour moi comment le clic se propage d'un fragment à une activité.
Andrew Queisser
15

Il y a de très bonnes réponses ici, mais je veux ajouter une ligne:

En android:onclickXML, Android utilise la réflexion java derrière la scène pour gérer cela.

Et comme expliqué ici, la réflexion ralentit toujours la performance. (en particulier sur Dalvik VM). L'inscription onClickListenerest une meilleure façon.

Krupal Shah
la source
5
Combien cela peut-il ralentir l'application? :) Une demi-milliseconde; pas même? Par rapport au gonflage de la disposition, c'est comme une plume et une baleine
Konrad Morawski
14

Notez que si vous souhaitez utiliser la fonctionnalité XML onClick, la méthode correspondante doit avoir un paramètre, dont le type doit correspondre à l'objet XML.

Par exemple, un bouton sera lié à votre méthode via sa chaîne de nom: android:onClick="MyFancyMethod"mais la déclaration de méthode doit afficher: ...MyFancyMethod(View v) {...

Si vous essayez d'ajouter cette fonctionnalité à un élément de menu , elle aura exactement la même syntaxe dans le fichier XML mais votre méthode sera déclarée comme:...MyFancyMethod(MenuItem mi) {...

Antoine Lizée
la source
6

Une autre façon de définir vos écouteurs au clic serait d'utiliser XML. Ajoutez simplement l'attribut android: onClick à votre balise.

C'est une bonne pratique d'utiliser l'attribut xml «onClick» sur une classe Java anonyme dans la mesure du possible.

Tout d'abord, regardons la différence de code:

Attribut XML / attribut onClick

Partie XML

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:id="@+id/button1" 
    android:onClick="showToast"/>

Portion Java

public void showToast(View v) {
    //Add some logic
}

Classe Java anonyme / setOnClickListener

Partie XML

<Button
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"/>

Portion Java

findViewById(R.id.button1).setOnClickListener(
    new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            //Add some logic
        }
});

Voici les avantages de l'utilisation de l'attribut XML sur une classe Java anonyme:

  • Avec la classe Java anonyme, nous devons toujours spécifier un identifiant pour nos éléments, mais avec l'identifiant d'attribut XML peut être omis.
  • Avec la classe Java anonyme, nous devons rechercher activement l'élément à l'intérieur de la vue (partie findViewById), mais avec l'attribut XML Android le fait pour nous.
  • La classe Java anonyme nécessite au moins 5 lignes de code, comme nous pouvons le voir, mais avec l'attribut XML, 3 lignes de code suffisent.
  • Avec la classe Java anonyme, nous devons nommer notre méthode "onClick", mais avec l'attribut XML, nous pouvons ajouter n'importe quel nom que nous voulons, ce qui aidera considérablement à la lisibilité de notre code.
  • L'attribut XML «onClick» a été ajouté par Google lors de la sortie de l'API niveau 4, ce qui signifie qu'il s'agit d'une syntaxe un peu plus moderne et que la syntaxe moderne est presque toujours meilleure.

Bien sûr, il n'est pas toujours possible d'utiliser l'attribut Xml, voici les raisons pour lesquelles nous ne le choisirions pas:

  • Si nous travaillons avec des fragments. L'attribut onClick ne peut être ajouté qu'à une activité, donc si nous avons un fragment, nous devons utiliser une classe anonyme.
  • Si nous souhaitons déplacer l'écouteur onClick vers une classe distincte (peut-être s'il est très compliqué et / ou si nous souhaitons le réutiliser dans différentes parties de notre application), nous ne voudrions pas utiliser l'attribut xml Soit.
Bestin John
la source
Et veuillez noter que la fonction appelée à l'aide d'attributs XML doit toujours être publique et si elles sont déclarées comme privées, cela entraînera une exception.
Bestin John
5

Avec Java 8, vous pourriez probablement utiliser la référence de méthode pour obtenir ce que vous voulez.

Supposons qu'il s'agit de votre onClickgestionnaire d'événements pour un bouton.

private void onMyButtonClicked(View v) {
    if (v.getId() == R.id.myButton) {
        // Do something when myButton was clicked
    }
}

Ensuite, vous passez la onMyButtonClickedréférence de méthode d'instance dans un setOnClickListener()appel comme celui-ci.

Button myButton = (Button) findViewById(R.id.myButton);
myButton.setOnClickListener(this::onMyButtonClicked);

Cela vous permettra d'éviter de définir explicitement une classe anonyme par vous-même. Je dois cependant souligner que la référence de méthode de Java 8 n'est en fait qu'un sucre syntaxique. Il crée en fait une instance de la classe anonyme pour vous (tout comme l'expression lambda), d'où la même prudence que le gestionnaire d'événements de style expression-lambda a été appliqué lorsque vous arrivez à l'annulation de l'enregistrement de votre gestionnaire d'événements. Cet article explique vraiment bien.

PS. Pour ceux qui veulent savoir comment puis-je vraiment utiliser la fonctionnalité du langage Java 8 dans Android, c'est une gracieuseté de la bibliothèque retrolambda .

onelaview
la source
5

En utilisant l'attribut XML, vous avez juste besoin de définir une méthode au lieu d'une classe, donc je me demandais si la même chose pouvait être faite via du code et non dans la disposition XML.

Oui, vous pouvez faire votre fragmentou activitymettre en œuvreView.OnClickListener

et lorsque vous initialisez vos nouveaux objets de vue dans le code, vous pouvez simplement faire mView.setOnClickListener(this);

et cela définit automatiquement tous les objets de vue dans le code pour utiliser la onClick(View v)méthode que votre fragmentou activityetc a.

pour distinguer quelle vue a appelé la onClickméthode, vous pouvez utiliser une instruction switch sur la v.getId()méthode.

Cette réponse est différente de celle qui dit "Non, ce n'est pas possible via le code"

CQM
la source
4
   Add Button in xml and give onclick attribute name that is the name of Method.
   <!--xml --!>
   <Button
  android:id="@+id/btn_register"
  android:layout_margin="1dp"
  android:onClick="addNumber"
  android:text="Add"
  />


    Button btnAdd = (Button) findViewById(R.id.mybutton); btnAdd.setOnClickListener(new View.OnClickListener() {
   @Override
    public void onClick(View v) {
      addNumber(v);
    }
    });

  Private void addNumber(View v){
  //Logic implement 
    switch (v.getId()) {
    case R.id.btnAdd :
        break;
     default:
        break;
    }}
jeet parmar
la source
3

Soutenant la réponse de Ruivo, oui, vous devez déclarer la méthode comme "publique" pour pouvoir l'utiliser dans le onclick XML d'Android - je développe une application ciblant du niveau d'API 8 (minSdk ...) à 16 (targetSdk ...).

Je déclarais ma méthode privée et cela a provoqué une erreur, je l'ai simplement déclarée comme étant de grands travaux publics.

Waqas Hasan
la source
il semble que les variables déclarées dans la classe de l'activité d'hébergement ne peuvent pas être utilisées dans la portée du rappel déclaré; le Bundle Activity.mBundle lèvera une exception IllegalStateException / NullPointerException s'il est utilisé dans myFancyMethod ().
Quasaur
2

Soyez prudent, bien que android:onClickXML semble être un moyen pratique de gérer les clics, l' setOnClickListenerimplémentation fait quelque chose de plus que l'ajout de onClickListener. En effet, cela a mis la propriété view clickableà true.

Bien que cela ne soit pas un problème sur la plupart des implémentations Android, selon le constructeur du téléphone, le bouton est toujours par défaut clickable = true mais d'autres constructeurs sur certains modèles de téléphone peuvent avoir un clickable = false par défaut sur les vues non Button.

Donc, définir le XML ne suffit pas, vous devez penser tout le temps à ajouter android:clickable="true"sur un bouton non, et si vous avez un appareil où la valeur par défaut est clickable = true et que vous oubliez même une fois de mettre cet attribut XML, vous ne remarquerez pas le problème lors de l'exécution, mais obtiendra les commentaires sur le marché lorsqu'il sera entre les mains de vos clients!

De plus, nous ne pouvons jamais être sûrs de la façon dont proguard masquera et renommera les attributs XML et la méthode de classe, donc pas sûr à 100% qu'ils n'auront jamais un bogue un jour.

Donc, si vous ne voulez jamais avoir de problèmes et n'y pensez jamais, il vaut mieux utiliser des setOnClickListenerbibliothèques comme ButterKnife avec annotation@OnClick(R.id.button)

Livio
la source
L' onClickattribut XML est également défini clickable = truecar il fait appel setOnClickListenerà l' Viewinterne
Florian Walther
1

Supposons que vous souhaitiez ajouter un événement de clic comme celui-ci main.xml

<Button
    android:id="@+id/btn_register"
    android:layout_margin="1dp"
    android:layout_marginLeft="3dp"
    android:layout_marginTop="10dp"
    android:layout_weight="2"
    android:onClick="register"
    android:text="Register"
    android:textColor="#000000"/>

Dans le fichier java, vous devez écrire une méthode comme cette méthode.

public void register(View view) {
}
nazrul islam
la source
0

Je suis Écrivez ce code dans un fichier xml ...

<Button
    android:id="@+id/btn_register"
    android:layout_margin="1dp"
    android:layout_marginLeft="3dp"
    android:layout_marginTop="10dp"
    android:layout_weight="2"
    android:onClick="register"
    android:text="Register"
    android:textColor="#000000"/>

Et écrivez ce code en fragment ...

public void register(View view) {
}
user2786249
la source
Ceci est possible en fragment.
user2786249
0

La meilleure façon de procéder consiste à utiliser le code suivant:

 Button button = (Button)findViewById(R.id.btn_register);
 button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //do your fancy method
            }
        });
Katarina Dabo
la source
Je ne vois pas comment cela répond à la question - le demandeur veut éviter de créer une classe anonyme et utiliser à la place une méthode de classe.
ajshort
0

Pour vous faciliter la vie et éviter la classe anonyme dans setOnClicklistener (), implémentez une interface View.OnClicklistener comme ci-dessous:

public class YourClass étend les implémentations CommonActivity View.OnClickListener, ...

cela évite:

btn.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        yourMethod(v);
    }
});

et va directement à:

@Override
public void onClick(View v) {
  switch (v.getId()) {
    case R.id.your_view:
      yourMethod();
      break;
  }
}
Vicente Domingos
la source