Wie genau unterscheidet sich das XML-Attribut android: onClick von setOnClickListener?

416

Ich habe gelesen, dass Sie onClickeiner Schaltfläche auf zwei Arten einen Handler zuweisen können.

Verwenden Sie das android:onClickXML-Attribut, bei dem Sie nur den Namen einer öffentlichen Methode mit der Signatur verwenden, void name(View v)oder verwenden Sie die setOnClickListenerMethode, bei der Sie ein Objekt übergeben, das die OnClickListenerSchnittstelle implementiert . Letzteres erfordert oft eine anonyme Klasse, die ich persönlich nicht mag (persönlicher Geschmack) oder die Definition einer internen Klasse, die das implementiert OnClickListener.

Wenn Sie das XML-Attribut verwenden, müssen Sie nur eine Methode anstelle einer Klasse definieren. Ich habe mich gefragt, ob dies auch über Code und nicht im XML-Layout möglich ist.

Emitrax
quelle
4
Ich habe dein Problem gelesen und ich denke, du steckst genau wie ich am selben Ort fest. Ich bin auf ein sehr gutes Video gestoßen, das mir bei der Lösung meines Problems sehr geholfen hat. Finden Sie das Video unter folgendem Link: youtube.com/watch?v=MtmHURWKCmg&feature=youtu.be Ich hoffe, dies wird Ihnen auch helfen :)
user2166292
9
Für diejenigen, die Zeit sparen möchten, wenn sie sich das im obigen Kommentar veröffentlichte Video ansehen, wird lediglich gezeigt, wie zwei Schaltflächen dieselbe Methode für das onClickAttribut in der Layoutdatei verwenden können. Dies geschieht dank des Parameters View v. Sie überprüfen einfach if (v == findViewById(R.id.button1)) etc ..
CodyBugstein
13
@Imray Ich würde denken, es ist besser zu verwenden v.getId() == R.id.button1, da Sie nicht die tatsächliche Kontrolle finden und einen Vergleich durchführen müssen. Und Sie können ein switchstatt vieler Wenns verwenden.
Sami Kuhmonen
3
Dieses Tutorial wird Ihnen sehr helfen. Klicken Sie hier
c49
Verwenden von XML android: onClick verursacht einen Absturz.

Antworten:

604

Nein, das ist per Code nicht möglich. Android implementiert das nur OnClickListenerfür Sie, wenn Sie das android:onClick="someMethod"Attribut definieren .

Diese beiden Codefragmente sind gleich und werden nur auf zwei verschiedene Arten implementiert.

Code-Implementierung

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
}

Oben ist eine Code-Implementierung von a OnClickListener. Und das ist die XML-Implementierung.

XML-Implementierung

<?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 -->

Im Hintergrund führt Android nichts anderes als den Java-Code aus und ruft Ihre Methode bei einem Klickereignis auf.

Beachten Sie, dass Android mit dem obigen XML nur in der aktuellen Aktivität nach der onClickMethode sucht myFancyMethod(). Dies ist wichtig zu beachten, wenn Sie Fragmente verwenden, da Android selbst dann, wenn Sie das obige XML mithilfe eines Fragments hinzufügen, nicht nach der onClickMethode in der .javaDatei des Fragments sucht, das zum Hinzufügen des XML verwendet wird.

Eine andere wichtige Sache, die mir aufgefallen ist. Sie haben erwähnt, dass Sie keine anonymen Methoden bevorzugen . Du wolltest sagen, dass du keine anonymen Klassen magst .

Octavian A. Damiean
quelle
4
Ich bin kein Java-Guru, aber ja, ich meinte anonyme Klasse. Danke für deine Antwort. Sehr deutlich.
Emitrax
118
Beachten Sie, dass Sie bei Verwendung des XML-Onclicks die onclick-Methode ( myFancyMethod()) in die aktuelle Aktivität einfügen müssen . Dies ist wichtig, wenn Sie Fragmente verwenden, da die programmgesteuerte Methode zum Festlegen von Onclick-Listenern wahrscheinlich die Methode zum Behandeln von Klicks in onCreateView () eines Fragments enthält ... wo es nicht gefunden wird, wenn es aus XML referenziert wird .
Peter Ajtai
12
Ja, die Methode muss öffentlich sein.
Octavian A. Damiean
12
Interessant ist, dass man im Code den Methodenzugriff abschirmen kann, indem man die Methode privat macht, während dies auf XML-Weise zur Offenlegung der Methode führt.
Bgse
5
Die Tatsache, dass die Funktion (im XML-Ansatz) in Aktivität sein muss, ist nicht nur wichtig, wenn Sie Fragmente berücksichtigen, sondern auch benutzerdefinierte Ansichten (die die Schaltfläche enthalten). Wenn Sie eine benutzerdefinierte Ansicht haben, die Sie in mehreren Aktivitäten wiederverwenden, aber in allen Fällen dieselbe onClick-Methode verwenden möchten, ist die XML-Methode nicht die bequemste. Sie müssten diese onClickMethod (mit demselben Text) in jede Aktivität einfügen, die Ihre benutzerdefinierte Ansicht verwendet.
Bartek Lipinski
87

Als ich die beste Antwort sah, wurde mir klar, dass mein Problem darin bestand, den Parameter (View v) nicht auf die ausgefallene Methode zu setzen:

public void myFancyMethod(View v) {}

Wenn Sie versuchen, über die XML darauf zuzugreifen, sollten Sie verwenden

android:onClick="myFancyMethod"/>

Hoffe das hilft jemandem.

jp093121
quelle
74

android:onClick ist für API-Level 4 und höher. Wenn Sie also auf <1,6 abzielen, können Sie es nicht verwenden.

James
quelle
33

Überprüfen Sie, ob Sie vergessen haben, die Methode öffentlich zu machen!

Ruivo
quelle
1
Warum ist es obligatorisch, es öffentlich zu machen?
eRaisedToX
3
@eRaisedToX Ich denke, es ist ziemlich klar: Wenn es nicht öffentlich ist, kann es nicht über das Android-Framework aufgerufen werden.
m0skit0
28

Das Angeben von android:onClickAttributen führt dazu, dass die ButtonInstanz setOnClickListenerintern aufgerufen wird . Daher gibt es absolut keinen Unterschied.

Lassen Sie uns zum klaren Verständnis sehen, wie das XML- onClickAttribut vom Framework behandelt wird.

Wenn eine Layoutdatei aufgeblasen wird, werden alle darin angegebenen Ansichten instanziiert. In diesem speziellen Fall wird die ButtonInstanz mit dem public Button (Context context, AttributeSet attrs, int defStyle)Konstruktor erstellt. Alle Attribute im XML-Tag werden aus dem Ressourcenpaket gelesen und an AttributeSetden Konstruktor übergeben.

ButtonKlasse wird von ViewKlasse geerbt , was dazu führt, dass der ViewKonstruktor aufgerufen wird, der dafür sorgt, dass der Click-Call-Back-Handler über festgelegt wird setOnClickListener.

Das Attribut onClick in definierten attrs.xml wird in View.java als bezeichnet R.styleable.View_onClick.

Hier ist der Code, der View.javadie meiste Arbeit für Sie erledigt, setOnClickListenerindem er selbst anruft .

 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;

Wie Sie sehen können, setOnClickListenerwird aufgerufen, um den Rückruf zu registrieren, wie wir es in unserem Code tun. Der einzige Unterschied ist, dass es verwendetJava Reflection , dass die in unserer Aktivität definierte Rückrufmethode aufgerufen wird.

Hier sind die Gründe für Probleme, die in anderen Antworten erwähnt werden:

  • Die Rückrufmethode sollte öffentlich sein : Da sie Java Class getMethodverwendet wird, wird nur nach Funktionen mit öffentlichem Zugriffsspezifizierer gesucht. Andernfalls seien Sie bereit, IllegalAccessExceptionAusnahmen zu behandeln .
  • Bei Verwendung von Button mit onClick in Fragment sollte der Rückruf unter Aktivität definiert werden : getContext().getClass().getMethod()Aufruf beschränkt die Methodensuche auf den aktuellen Kontext, dh Aktivität bei Fragment. Daher wird die Methode innerhalb der Aktivitätsklasse und nicht innerhalb der Fragmentklasse gesucht.
  • Callback-Methode sollte View-Parameter akzeptieren : Da Java Class getMethodnach einer Methode gesucht wird, die View.classals Parameter akzeptiert .
Manish Mulimani
quelle
1
Das war das fehlende Teil für mich - Java verwendet Reflection, um den Click-Handler zu finden, der mit getContext () beginnt. Es war für mich ein wenig mysteriös, wie sich der Klick von einem Fragment zu einer Aktivität ausbreitet.
Andrew Queisser
15

Hier gibt es sehr gute Antworten, aber ich möchte eine Zeile hinzufügen:

In android:onclickXML verwendet Android die Java-Reflexion hinter den Kulissen, um dies zu handhaben.

Und wie hier erklärt, verlangsamt die Reflexion immer die Leistung. (besonders auf Dalvik VM). Die Registrierung onClickListenerist ein besserer Weg.

Krupal Shah
quelle
5
Wie viel kann es die App verlangsamen? :) Eine halbe Millisekunde; nicht mal? Im Vergleich zum tatsächlichen Aufblasen des Layouts ist es wie eine Feder und ein Wal
Konrad Morawski
14

Beachten Sie, dass die entsprechende Methode einen Parameter haben sollte, dessen Typ mit dem XML-Objekt übereinstimmen sollte, wenn Sie die onClick-XML-Funktion verwenden möchten.

Beispielsweise wird eine Schaltfläche über ihre Namenszeichenfolge mit Ihrer Methode verknüpft. android:onClick="MyFancyMethod"Die Methodendeklaration sollte jedoch Folgendes anzeigen: ...MyFancyMethod(View v) {...

Wenn Sie versuchen, diese Funktion einem Menüelement hinzuzufügen , hat sie genau dieselbe Syntax in der XML-Datei, aber Ihre Methode wird wie folgt deklariert:...MyFancyMethod(MenuItem mi) {...

Antoine Lizée
quelle
6

Eine andere Möglichkeit, Ihre On-Click-Listener festzulegen, ist die Verwendung von XML. Fügen Sie Ihrem Tag einfach das Attribut android: onClick hinzu.

Es wird empfohlen, das XML-Attribut "onClick" nach Möglichkeit über einer anonymen Java-Klasse zu verwenden.

Schauen wir uns zunächst den Unterschied im Code an:

XML-Attribut / onClick-Attribut

XML-Teil

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

Java-Teil

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

Anonyme Java-Klasse / setOnClickListener

XML-Teil

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

Java-Teil

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

Die Verwendung des XML-Attributs gegenüber einer anonymen Java-Klasse bietet folgende Vorteile:

  • Bei der anonymen Java-Klasse müssen wir immer eine ID für unsere Elemente angeben, aber bei der XML-Attribut-ID kann diese weggelassen werden.
  • Bei der anonymen Java-Klasse müssen wir aktiv nach dem Element in der Ansicht suchen (findViewById-Teil), aber mit dem XML-Attribut erledigt Android dies für uns.
  • Die anonyme Java-Klasse benötigt, wie wir sehen können, mindestens 5 Codezeilen, aber mit dem XML-Attribut sind 3 Codezeilen ausreichend.
  • Bei der anonymen Java-Klasse müssen wir unsere Methode "onClick" benennen, aber mit dem XML-Attribut können wir jeden gewünschten Namen hinzufügen, was die Lesbarkeit unseres Codes erheblich verbessert.
  • Das XML-Attribut "onClick" wurde von Google während der API-Version 4 hinzugefügt. Dies bedeutet, dass die Syntax etwas moderner und die moderne Syntax fast immer besser ist.

Natürlich ist es nicht immer möglich, das Xml-Attribut zu verwenden. Hier sind die Gründe, warum wir es nicht gewählt haben:

  • Wenn wir mit Fragmenten arbeiten. Das onClick-Attribut kann nur zu einer Aktivität hinzugefügt werden. Wenn wir also ein Fragment haben, müssten wir eine anonyme Klasse verwenden.
  • Wenn wir den onClick-Listener in eine separate Klasse verschieben möchten (möglicherweise, wenn er sehr kompliziert ist und / oder in verschiedenen Teilen unserer Anwendung wiederverwendet werden soll), möchten wir das XML-Attribut nicht verwenden entweder.
Bestin John
quelle
Beachten Sie außerdem, dass die mit XML-Attributen aufgerufene Funktion immer öffentlich sein sollte. Wenn sie als private Henne deklariert wird, führt dies zu einer Ausnahme.
Bestin John
5

Mit Java 8 könnten Sie wahrscheinlich die Methodenreferenz verwenden , um das zu erreichen, was Sie wollen.

Angenommen, dies ist Ihr onClickEvent-Handler für eine Schaltfläche.

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

Anschließend übergeben Sie onMyButtonClickedin einem setOnClickListener()Aufruf wie diesem die Instanzmethodenreferenz .

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

Auf diese Weise können Sie vermeiden , eine anonyme Klasse explizit selbst zu definieren. Ich muss jedoch betonen, dass die Methodenreferenz von Java 8 eigentlich nur ein syntaktischer Zucker ist. Es wird tatsächlich eine Instanz der anonymen Klasse für Sie erstellt (genau wie es der Lambda-Ausdruck getan hat), daher wurde eine ähnliche Vorsicht angewendet wie beim Ereignishandler im Lambda-Ausdrucksstil, wenn Sie die Registrierung Ihres Ereignishandlers aufheben. Dieser Artikel erklärt es wirklich schön.

PS. Für diejenigen, die neugierig sind, wie ich die Java 8- Sprachfunktion in Android wirklich nutzen kann, ist dies eine Retrolambda- Bibliothek.

onelaview
quelle
5

Wenn Sie das XML-Attribut verwenden, müssen Sie nur eine Methode anstelle einer Klasse definieren. Ich habe mich gefragt, ob dies auch über Code und nicht im XML-Layout möglich ist.

Ja, Sie können Ihre erstellen fragmentoder activityimplementierenView.OnClickListener

und wenn Sie Ihre neuen Ansichtsobjekte im Code initialisieren, können Sie dies einfach tun mView.setOnClickListener(this);

Dadurch werden automatisch alle Ansichtsobjekte im Code so eingestellt, dass sie die onClick(View v)Methode verwenden, die Ihr fragmentoder activityusw. hat.

Um zu unterscheiden, welche Ansicht die onClickMethode aufgerufen hat , können Sie eine switch-Anweisung für die v.getId()Methode verwenden.

Diese Antwort unterscheidet sich von der Antwort "Nein, das ist per Code nicht möglich".

CQM
quelle
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
quelle
3

Ja, Sie müssen die Methode von Ruivo als "öffentlich" deklarieren, um sie in Android XML onclick verwenden zu können. Ich entwickle eine App, die auf API Level 8 (minSdk ...) bis 16 (targetSdk ...) abzielt.

Ich habe meine Methode als privat deklariert und sie hat Fehler verursacht. Ich habe sie nur als öffentlich deklariert. Das funktioniert großartig.

Waqas Hasan
quelle
Es scheint, dass Variablen, die in der Klasse der Hosting-Aktivität deklariert sind, nicht im Rahmen des deklarierten Rückrufs verwendet werden können. Das Bundle Activity.mBundle löst eine IllegalStateException / NullPointerException aus, wenn es in myFancyMethod () verwendet wird.
Quasaur
2

Seien Sie vorsichtig, obwohl android:onClickXML eine bequeme Möglichkeit zu sein scheint, mit Klicks umzugehen, führt die setOnClickListenerImplementierung etwas anderes aus als das Hinzufügen von onClickListener. In der Tat hat es die Eigenschaft view clickableauf true gesetzt.

Während dies bei den meisten Android-Implementierungen möglicherweise kein Problem darstellt, ist die Schaltfläche laut Telefonkonstruktor standardmäßig immer klickbar = wahr, während andere Konstruktoren in einigen Telefonmodellen in Ansichten ohne Schaltflächen möglicherweise standardmäßig klickbar = falsch sind.

Das Einstellen der XML-Datei reicht also nicht aus. Sie müssen die ganze Zeit überlegen, um sie hinzuzufügen android:clickable="true" Nicht-Schaltfläche Wenn Sie ein Gerät haben, auf dem die Standardeinstellung anklickbar ist = true, und Sie einmal vergessen, dieses XML-Attribut einzufügen, werden Sie es nicht bemerken Das Problem zur Laufzeit wird aber das Feedback auf den Markt bekommen, wenn es in den Händen Ihrer Kunden liegt!

Darüber hinaus können wir nie sicher sein, wie Proguard XML-Attribute und Klassenmethoden verschleiert und umbenennt, sodass sie nicht 100% sicher sind, dass sie eines Tages niemals einen Fehler haben werden.

Wenn Sie also nie Probleme haben und nie darüber nachdenken möchten, ist es besser, setOnClickListenerBibliotheken wie ButterKnife mit Anmerkungen zu verwenden@OnClick(R.id.button)

Livio
quelle
Das onClickXML - Attribut setzt auch , clickable = trueweil es ruft setOnClickListenerauf der Viewintern
Florian Walther
1

Angenommen, Sie möchten ein solches Klickereignis hinzufügen 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"/>

In einer Java-Datei müssen Sie eine Methode wie diese schreiben.

public void register(View view) {
}
Nazrul Islam
quelle
0

Ich schreibe diesen Code in eine XML-Datei ...

<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"/>

Und schreibe diesen Code in Fragment ...

public void register(View view) {
}
user2786249
quelle
Dies ist fragmentarisch möglich.
user2786249
0

Der beste Weg, dies zu tun, ist mit dem folgenden Code:

 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
quelle
Ich sehe nicht, wie dies die Frage beantwortet - der Fragesteller möchte vermeiden, eine anonyme Klasse zu erstellen, und stattdessen eine Klassenmethode verwenden.
Kurz
0

Implementieren Sie eine View.OnClicklistener-Schnittstelle wie folgt, um Ihnen das Leben zu erleichtern und die anonyme Klasse in setOnClicklistener () zu vermeiden:

öffentliche Klasse YourClass erweitert CommonActivity implementiert View.OnClickListener, ...

Dies vermeidet:

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

und geht direkt zu:

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