In cosa differisce esattamente l'attributo XML android: onClick da setOnClickListener?


416

Da quello che ho letto, puoi assegnare un onClickgestore a un pulsante in due modi.

Utilizzando l' android:onClickattributo XML in cui si utilizza semplicemente il nome di un metodo pubblico con la firma void name(View v)o utilizzando il setOnClickListenermetodo in cui si passa un oggetto che implementa l' OnClickListenerinterfaccia. Quest'ultimo richiede spesso una classe anonima che personalmente non mi piace (gusto personale) o che definisce una classe interna che implementa il OnClickListener.

Usando l'attributo XML devi solo definire un metodo anziché una classe, quindi mi chiedevo se lo stesso può essere fatto tramite codice e non nel layout XML.


4
Ho letto il tuo problema e penso che tu sia bloccato nello stesso posto proprio come me. Mi sono imbattuto in un ottimo video che mi ha aiutato moltissimo a risolvere il mio problema. Trova il video sul seguente link: youtube.com/watch?v=MtmHURWKCmg&feature=youtu.be Spero che questo ti possa aiutare anche :) :)
user2166292

9
Per coloro che vogliono risparmiare tempo guardando il video pubblicato nel commento sopra, dimostra semplicemente come due pulsanti possono avere lo stesso metodo per l' onClickattributo nel file di layout. Questo viene fatto grazie al parametro View v. Basta controllare if (v == findViewById(R.id.button1)) ecc.
CodyBugstein,

13
@Imray Penso che sia meglio usare v.getId() == R.id.button1, dal momento che non devi trovare il controllo effettivo e fare un confronto. E puoi usare un switchif anziché molti if.
Sami Kuhmonen,

3
Questo tutorial ti aiuterà molto. clicca qui
c49

Utilizzando xml android: onClick provoca un arresto anomalo.

Risposte:


604

No, ciò non è possibile tramite il codice. Android implementa il comando OnClickListenerper te quando definisci l' android:onClick="someMethod"attributo.

Quei due frammenti di codice sono uguali, implementati in due modi diversi.

Implementazione del codice

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
}

Sopra è un'implementazione del codice di un OnClickListener. E questa è l'implementazione XML.

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

Sullo sfondo, Android non fa altro che il codice Java, chiamando il tuo metodo su un evento click.

Si noti che con l'XML sopra, Android cercherà il onClickmetodo myFancyMethod()solo nell'attività corrente. Questo è importante da ricordare se si utilizzano frammenti, poiché anche se si aggiunge l'XML sopra usando un frammento, Android non cercherà il onClickmetodo nel .javafile del frammento utilizzato per aggiungere l'XML.

Un'altra cosa importante che ho notato. Hai detto che non preferisci metodi anonimi . Volevi dire che non ti piacciono le lezioni anonime .


4
Non sono un guru di Java ma sì, intendevo classe anonima. Grazie per la tua risposta. Molto chiaro.
emitrax,

118
Si noti che se si utilizza il onclick XML, è necessario inserire il metodo onclick ( myFancyMethod()) nell'attività corrente. Questo è importante se si utilizza frammenti, dal momento che il modo programmatico di impostare onclick ascoltatori probabilmente avrà la movimentazione clic in onCreateView di un frammento () metodo ... dove sarebbe non essere trovato se di cui da XML.
Peter Ajtai,

12
Sì, il metodo deve essere pubblico.
Ottaviano A. Damiano,

12
La cosa interessante è che farlo nel codice consente di proteggere l'accesso al metodo rendendolo privato, mentre farlo nel modo xml porta all'esposizione del metodo.
bgse

5
Il fatto che la funzione (nell'approccio XML) debba essere in Attività è importante non solo quando si considerano i frammenti, ma anche le viste personalizzate (contenenti il ​​pulsante). Quando si dispone di una vista personalizzata che si riutilizza in più attività, ma si desidera utilizzare lo stesso metodo onClick per tutti i casi, il metodo XML non è il più conveniente. Dovresti mettere questo onClickMethod (con lo stesso corpo) in ogni attività che utilizza la tua vista personalizzata.
Bartek Lipinski,

87

Quando ho visto la risposta migliore, mi sono reso conto che il mio problema non era quello di mettere il parametro (Visualizza v) sul metodo di fantasia:

public void myFancyMethod(View v) {}

Quando si tenta di accedervi dall'xml, si dovrebbe usare

android:onClick="myFancyMethod"/>

Spero che aiuti qualcuno.


74

android:onClick è dal livello API 4 in poi, quindi se hai come target <1.6, non puoi usarlo.


33

Controlla se hai dimenticato di rendere pubblico il metodo!


1
perché è obbligatorio renderlo pubblico?
eRaisedToX

3
@eRaisedToX Penso che sia abbastanza chiaro: se non è pubblico non può essere chiamato dal framework Android.
m0skit0,

28

Se si specifica l' android:onClickattributo, l' Buttonistanza si chiama setOnClickListenerinternamente Quindi non c'è assolutamente alcuna differenza.

Per avere una chiara comprensione, vediamo come onClickviene gestito l'attributo XML dal framework.

Quando un file di layout viene gonfiato, vengono istanziate tutte le viste specificate in esso. In questo caso specifico, l' Buttonistanza viene creata utilizzando il public Button (Context context, AttributeSet attrs, int defStyle)costruttore. Tutti gli attributi nel tag XML vengono letti dal pacchetto di risorse e passati AttributeSetal costruttore.

ButtonLa classe viene ereditata dalla Viewclasse che risulta nella Viewchiamata del costruttore, che si occupa di impostare il gestore di richiamata click via setOnClickListener.

L'attributo onClick definito in attrs.xml è indicato in View.java come R.styleable.View_onClick.

Ecco il codice View.javache fa la maggior parte del lavoro per te chiamando setOnClickListenerda solo.

 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;

Come puoi vedere, setOnClickListenerviene chiamato per registrare il callback, come facciamo nel nostro codice. L'unica differenza è che utilizza Java Reflectionper invocare il metodo di callback definito nella nostra Attività.

Ecco il motivo dei problemi menzionati in altre risposte:

  • Il metodo di callback dovrebbe essere pubblico : poiché Java Class getMethodviene utilizzato, vengono cercate solo le funzioni con identificatore di accesso pubblico. Altrimenti sii pronto a gestire l' IllegalAccessExceptioneccezione.
  • Mentre si utilizza Button con onClick in Fragment, il callback deve essere definito in Activity : getContext().getClass().getMethod()call limita la ricerca del metodo al contesto corrente, che è Activity in caso di Fragment. Quindi il metodo viene cercato all'interno della classe Activity e non nella classe Fragment.
  • Il metodo di callback dovrebbe accettare Visualizza parametro : Poiché Java Class getMethodcerca un metodo che accetta View.classcome parametro.

1
Quello era il pezzo mancante per me - Java usa Reflection per trovare il gestore di clic che inizia con getContext (). Per me è stato un po 'misterioso come il clic si propaghi da un frammento a un'attività.
Andrew Queisser,

15

Ci sono risposte molto buone qui, ma voglio aggiungere una riga:

In android:onclickXML, Android utilizza il riflesso Java dietro la scena per gestire questo.

E come spiegato qui, la riflessione rallenta sempre le prestazioni. (specialmente su Dalvik VM). La registrazione onClickListenerè un modo migliore.


5
Quanto può rallentare l'app? :) Mezzo millisecondo; nemmeno? In confronto a gonfiare effettivamente il layout è come una piuma e una balena
Konrad Morawski,

14

Tieni presente che se desideri utilizzare la funzione XML onClick, il metodo corrispondente dovrebbe avere un parametro, il cui tipo deve corrispondere all'oggetto XML.

Ad esempio, un pulsante verrà collegato al metodo tramite la stringa del nome: android:onClick="MyFancyMethod"ma la dichiarazione del metodo dovrebbe mostrare: ...MyFancyMethod(View v) {...

Se stai cercando di aggiungere questa funzione a una voce di menu , avrà la stessa sintassi esatta nel file XML ma il tuo metodo sarà dichiarato come:...MyFancyMethod(MenuItem mi) {...


6

Un altro modo per impostare i listener al clic sarebbe utilizzare XML. Aggiungi semplicemente l'attributo android: onClick al tuo tag.

È buona norma utilizzare l'attributo xml "onClick" su una classe Java anonima quando possibile.

Prima di tutto, diamo un'occhiata alla differenza di codice:

Attributo XML / attributo onClick

Porzione XML

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

Porzione Java

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

Classe / setOnClickListener anonimi Java

Porzione XML

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

Porzione Java

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

Ecco i vantaggi dell'utilizzo dell'attributo XML su una classe Java anonima:

  • Con la classe Java anonima dobbiamo sempre specificare un ID per i nostri elementi, ma con l'attributo XML è possibile omettere l'id.
  • Con la classe Java anonima dobbiamo cercare attivamente l'elemento all'interno della vista (porzione findViewById), ma con l'attributo XML Android lo fa per noi.
  • La classe anonima Java richiede almeno 5 righe di codice, come possiamo vedere, ma con l'attributo XML sono sufficienti 3 righe di codice.
  • Con la classe Java anonima dobbiamo nominare il nostro metodo "onClick", ma con l'attributo XML possiamo aggiungere qualsiasi nome desideriamo, il che ci aiuterà notevolmente nella leggibilità del nostro codice.
  • L'attributo XML "onClick" è stato aggiunto da Google durante la versione API livello 4, il che significa che è una sintassi un po 'più moderna e la sintassi moderna è quasi sempre migliore.

Naturalmente, non è sempre possibile utilizzare l'attributo Xml, ecco i motivi per cui non lo avremmo scelto:

  • Se stiamo lavorando con frammenti. L'attributo onClick può essere aggiunto solo a un'attività, quindi se abbiamo un frammento, dovremmo usare una classe anonima.
  • Se desideriamo spostare il listener onClick in una classe separata (forse se è molto complicato e / o vorremmo riutilizzarlo in diverse parti della nostra applicazione), allora non vorremmo usare l'attributo xml o.

E tieni presente che la funzione chiamata utilizzando gli attributi XML dovrebbe essere sempre pubblica e se vengono dichiarati come gallina privata comporterà un'eccezione.
Bestin John,

5

Con Java 8, probabilmente potresti usare il metodo di riferimento per ottenere ciò che desideri.

Supponiamo che questo sia il onClickgestore dell'evento per un pulsante.

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

Quindi, si passa il onMyButtonClickedriferimento al metodo dell'istanza in una setOnClickListener()chiamata come questa.

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

Ciò ti consentirà di evitare di definire esplicitamente una classe anonima da solo. Devo tuttavia sottolineare che il metodo di riferimento di Java 8 è in realtà solo uno zucchero sintattico. In realtà crea un'istanza della classe anonima per te (proprio come ha fatto l'espressione lambda) quindi un'attenzione simile a quella del gestore di eventi in stile lambda-expression è stato applicato quando si arriva all'annullamento della registrazione del gestore di eventi. Questo articolo lo spiega davvero bene.

PS. Per coloro che sono curiosi di sapere come posso davvero utilizzare la funzione del linguaggio Java 8 in Android, è una cortesia della libreria retrolambda .


5

Usando l'attributo XML devi solo definire un metodo anziché una classe, quindi mi chiedevo se lo stesso può essere fatto tramite codice e non nel layout XML.

Sì, puoi creare fragmento activityimplementareView.OnClickListener

e quando inizializzi i tuoi nuovi oggetti di visualizzazione nel codice, puoi semplicemente farlo mView.setOnClickListener(this);

e questo imposta automaticamente tutti gli oggetti di visualizzazione nel codice per utilizzare il onClick(View v)metodo utilizzato da etc fragmento activity.

per distinguere quale vista ha chiamato il onClickmetodo, è possibile utilizzare un'istruzione switch sul v.getId()metodo.

Questa risposta è diversa da quella che dice "No che non è possibile tramite il codice"


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;
    }}

3

A supporto della risposta di Ruivo, sì, devi dichiarare il metodo come "pubblico" per poterlo utilizzare nel click on XML di Android - Sto sviluppando un'app di targeting da API Level 8 (minSdk ...) a 16 (targetSdk ...).

Stavo dichiarando il mio metodo come privato e ha causato errori, semplicemente dichiarandolo come pubblico funziona alla grande.


sembra che le variabili dichiarate nella classe Attività di hosting non possano essere utilizzate nell'ambito del callback dichiarato; Bundle Activity.mBundle genererà IllegalStateException / NullPointerException se utilizzato in myFancyMethod ().
Quasaur,

2

Fai attenzione, anche se android:onClickXML sembra essere un modo conveniente per gestire i clic, l' setOnClickListenerimplementazione fa qualcosa in più rispetto all'aggiunta dionClickListener . In effetti, ha messo la proprietà view clickablesu true.

Sebbene non sia un problema sulla maggior parte delle implementazioni Android, secondo il costruttore del telefono, il pulsante è sempre predefinito su clickable = true, ma altri costruttori su alcuni modelli di telefono potrebbero avere un clickable predefinito = false su visualizzazioni non Button.

Quindi l'impostazione dell'XML non è sufficiente, devi sempre pensare di aggiungere android:clickable="true" pulsante non, e se hai un dispositivo in cui il valore predefinito è cliccabile = vero e dimentichi anche una volta di mettere questo attributo XML, non noterai il problema in fase di esecuzione ma otterrà il feedback sul mercato quando sarà nelle mani dei tuoi clienti!

Inoltre, non possiamo mai essere sicuri di come proguard offuscerà e rinominerà gli attributi XML e il metodo di classe, quindi non è sicuro al 100% che non avranno mai un bug un giorno.

Quindi, se non vuoi mai avere problemi e non pensarci mai, è meglio usare setOnClickListenerlibrerie come ButterKnife con annotazioni@OnClick(R.id.button)


L' onClickattributo XML imposta anche clickable = trueperché richiama setOnClickListenersul Viewinternamente
Florian Walther

1

Supponiamo che tu voglia aggiungere un evento click come questo 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"/>

Nel file java, devi scrivere un metodo come questo metodo.

public void register(View view) {
}

0

Scrivo questo codice nel file 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"/>

E scrivi questo codice in frammenti ...

public void register(View view) {
}

Questo è possibile in frammenti.
user2786249

0

Il modo migliore per farlo è con il seguente codice:

 Button button = (Button)findViewById(R.id.btn_register);
 button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //do your fancy method
            }
        });

Non vedo come questo risponda alla domanda: chi chiede vuole evitare di creare una classe anonima e invece usa un metodo di classe.
ajshort,

0

Per semplificarti la vita ed evitare la classe anonima in setOnClicklistener (), implementa un'interfaccia View.OnClicklistener come di seguito:

classe pubblica YourClass estende CommonActivity implementa View.OnClickListener, ...

questo evita:

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

e va direttamente a:

@Override
public void onClick(View v) {
  switch (v.getId()) {
    case R.id.your_view:
      yourMethod();
      break;
  }
}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.