Czym dokładnie atrybut Android: onClick XML różni się od setOnClickListener?

416

Z tego, co przeczytałem, możesz przypisać onClickprogramator do przycisku na dwa sposoby.

Za pomocą android:onClickatrybutu XML, w którym po prostu używasz nazwy metody publicznej z podpisem void name(View v)lub za pomocą setOnClickListenermetody, w której przekazujesz obiekt implementujący OnClickListenerinterfejs. Ta ostatnia często wymaga anonimowej klasy, której osobiście nie lubię (osobisty gust) lub definiowania wewnętrznej klasy, która implementuje OnClickListener.

Używając atrybutu XML, wystarczy zdefiniować metodę zamiast klasy, więc zastanawiałem się, czy to samo można zrobić za pomocą kodu, a nie w układzie XML.

emitrax
źródło
4
Przeczytałem twój problem i myślę, że utknąłeś w tym samym miejscu, tak jak ja. Natrafiłem na bardzo dobry film, który bardzo pomógł mi w rozwiązaniu mojego problemu. Znajdź film na poniższym linku: youtube.com/watch?v=MtmHURWKCmg&feature=youtu.be Mam nadzieję, że to pomoże, jak również :)
user2166292
9
Dla tych, którzy chcą zaoszczędzić czas na oglądaniu filmu zamieszczonego w powyższym komentarzu, po prostu pokazuje, jak dwa przyciski mogą mieć tę samą metodę dla jego onClickatrybutu w pliku układu. Odbywa się to dzięki parametrowi View v. Po prostu sprawdź if (v == findViewById(R.id.button1)) itp.
CodyBugstein
13
@Imray Myślę, że lepiej jest użyć v.getId() == R.id.button1, ponieważ nie musisz znaleźć faktycznej kontroli i dokonać porównania. I możesz użyć switchzamiast wielu ifs.
Sami Kuhmonen
3
Ten samouczek bardzo ci pomoże. kliknij tutaj
c49,
Korzystanie z XML Android: onClick powoduje awarię.

Odpowiedzi:

604

Nie, nie jest to możliwe za pomocą kodu. Android po prostu implementuje OnClickListenerdla ciebie, gdy definiujesz android:onClick="someMethod"atrybut.

Te dwa fragmenty kodu są równe, tylko zaimplementowane na dwa różne sposoby.

Implementacja kodu

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
}

Powyżej znajduje się implementacja kodu OnClickListener. I to jest implementacja XML.

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

W tle Android nie robi nic poza kodem Java, wywołując metodę zdarzenia zdarzenia kliknięcia.

Pamiętaj, że w powyższym pliku XML system Android będzie szukał onClickmetody myFancyMethod()tylko w bieżącym działaniu. Ważne jest, aby pamiętać, jeśli używasz fragmentów, ponieważ nawet jeśli dodasz XML powyżej za pomocą fragmentu, Android nie będzie szukał onClickmetody w .javapliku fragmentu użytego do dodania XML.

Kolejną ważną rzeczą, którą zauważyłem. Wspomniałeś, że nie preferujesz anonimowych metod . Chciałeś powiedzieć, że nie lubisz anonimowych zajęć .

Oktawian A. Damiean
źródło
4
Nie jestem guru Java, ale tak, miałem na myśli anonimową klasę. Dzięki za odpowiedź. Bardzo czyste.
emitrax
118
Zauważ, że jeśli używasz XML onclick, musisz umieścić metodę onclick ( myFancyMethod()) w bieżącym działaniu. Jest to ważne, jeśli używasz fragmentów, ponieważ programowy sposób ustawiania detektorów onclick prawdopodobnie będzie miał metodę obsługującą kliknięcia w elemencie onCreateView () fragmentu, w którym nie można go znaleźć, jeśli odwołuje się do niego z XML.
Peter Ajtai
12
Tak, metoda musi być publiczna.
Octavian A. Damiean
12
Ciekawe jest to, że robienie tego w kodzie pozwala na osłonięcie dostępu do metody poprzez ustawienie metody prywatnej, podczas gdy robienie tego w sposób xml prowadzi do ujawnienia metody.
bgse
5
Fakt, że funkcja (w podejściu XML) musi znajdować się w działaniu, jest ważny nie tylko przy rozważaniu fragmentów, ale także widoków niestandardowych (zawierających przycisk). Jeśli masz widok niestandardowy, którego ponownie używasz w wielu działaniach, ale chcesz użyć tej samej metody onClick we wszystkich przypadkach, metoda XML nie jest najwygodniejsza. Będziesz musiał umieścić to onClickMethod (z tym samym ciałem) we wszystkich działaniach korzystających z widoku niestandardowego.
Bartek Lipiński
87

Kiedy zobaczyłem najwyższą odpowiedź, uświadomiłem sobie, że moim problemem nie było umieszczenie parametru (View v) w fantazyjnej metodzie:

public void myFancyMethod(View v) {}

Próbując uzyskać do niego dostęp z xml, należy użyć

android:onClick="myFancyMethod"/>

Mam nadzieję, że komuś pomoże.

jp093121
źródło
74

android:onClick dotyczy interfejsu API od poziomu 4, więc jeśli celujesz na <1,6, nie możesz go użyć.

James
źródło
33

Sprawdź, czy zapomniałeś podać metodę do publicznej wiadomości!

Ruivo
źródło
1
dlaczego upublicznienie jest obowiązkowe?
eRaisedToX
3
@eRaisedToX Myślę, że jest całkiem jasne: jeśli nie jest publiczny, nie można go wywołać ze środowiska Android.
m0skit0
28

Podanie android:onClickatrybutu powoduje wewnętrzne Buttonwywołanie instancji setOnClickListener. Dlatego nie ma absolutnie żadnej różnicy.

Aby mieć wyraźne zrozumienie, zobaczmy, jak środowisko XML onClickobsługuje atrybut.

Podczas nadmuchiwania pliku układu tworzone są wszystkie określone w nim widoki. W tym konkretnym przypadku Buttoninstancja jest tworzona za pomocą public Button (Context context, AttributeSet attrs, int defStyle)konstruktora. Wszystkie atrybuty w znaczniku XML są odczytywane z pakietu zasobów i przekazywane AttributeSetdo konstruktora.

Buttonklasa jest dziedziczona z Viewklasy, co powoduje Viewwywołanie konstruktora, który dba o ustawienie funkcji obsługi wywołania zwrotnego za pośrednictwem setOnClickListener.

Atrybut onClick zdefiniowany w pliku attrs.xml jest nazywany w View.java jako R.styleable.View_onClick.

Oto kod, View.javaktóry wykonuje większość pracy dla Ciebie, dzwoniąc setOnClickListenersam.

 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;

Jak widać, setOnClickListenerjest wywoływany w celu zarejestrowania oddzwaniania, tak jak robimy to w naszym kodzie. Jedyną różnicą jest to, że używa Java Reflectionmetody wywołania zwrotnego zdefiniowanej w naszym działaniu.

Oto powód problemów wymienionych w innych odpowiedziach:

  • Metoda wywołania zwrotnego powinna być publiczna : ponieważ Java Class getMethodjest używana, wyszukiwane są tylko funkcje ze specyfikatorem publicznego dostępu. W przeciwnym razie przygotuj się na IllegalAccessExceptionwyjątek.
  • Podczas korzystania z przycisku z onClick we fragmencie, wywołanie zwrotne powinno być zdefiniowane w Activity : getContext().getClass().getMethod()call ogranicza wyszukiwanie metod do bieżącego kontekstu, którym jest Activity w przypadku Fragmentu. Dlatego metoda jest przeszukiwana w obrębie klasy Activity, a nie klasy Fragment.
  • Metoda wywołania zwrotnego powinna zaakceptować Wyświetl parametr : Ponieważ Java Class getMethodwyszukuje metodę, która przyjmuje View.classjako parametr.
Manish Mulimani
źródło
1
To był dla mnie brakujący element - Java używa Reflection, aby znaleźć moduł obsługi kliknięć zaczynający się od getContext (). To było dla mnie trochę tajemnicze, jak kliknięcie przechodzi z fragmentu do działania.
Andrew Queisser,
15

Tutaj są bardzo dobre odpowiedzi, ale chcę dodać jedną linię:

W android:onclickXML, Android używa java refleksji za sceną do obsługi tego.

I jak wyjaśniono tutaj, odbicie zawsze spowalnia działanie. (szczególnie w Dalvik VM). Rejestracja onClickListenerjest lepszym sposobem.

Krupal Shah
źródło
5
Jak bardzo może to spowolnić aplikację? :) Pół milisekundy; nawet nie? W porównaniu do faktycznego nadmuchania układu jest jak piórko i wieloryb
Konrad Morawski
14

Pamiętaj, że jeśli chcesz użyć funkcji XML onClick, odpowiednia metoda powinna mieć jeden parametr, którego typ powinien być zgodny z obiektem XML.

Na przykład przycisk zostanie połączony z twoją metodą poprzez ciąg nazwy: android:onClick="MyFancyMethod"ale deklaracja metody powinna pokazywać: ...MyFancyMethod(View v) {...

Jeśli próbujesz dodać tę funkcję do elementu menu , będzie ona miała dokładnie taką samą składnię w pliku XML, ale twoja metoda zostanie zadeklarowana jako:...MyFancyMethod(MenuItem mi) {...

Antoine Lizée
źródło
6

Innym sposobem ustawienia nasłuchiwaczy kliknięć byłoby użycie XML. Wystarczy dodać atrybut android: onClick do tagu.

Dobrą praktyką jest używanie atrybutu xml „onClick” nad anonimową klasą Java, gdy tylko jest to możliwe.

Przede wszystkim spójrzmy na różnicę w kodzie:

Atrybut XML / atrybut onClick

Część XML

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

Część Java

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

Anonimowa klasa Java / setOnClickListener

Część XML

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

Część Java

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

Oto zalety korzystania z atrybutu XML nad anonimową klasą Java:

  • W przypadku anonimowej klasy Java zawsze musimy określić identyfikator dla naszych elementów, ale w przypadku atrybutu XML identyfikator można pominąć.
  • Dzięki anonimowej klasie Java musimy aktywnie wyszukiwać element wewnątrz widoku (część findViewById), ale dzięki atrybutowi XML Android robi to za nas.
  • Anonimowa klasa Java wymaga, jak widzimy, co najmniej 5 linii kodu, ale w przypadku atrybutu XML wystarczają 3 linie kodu.
  • W przypadku Anonimowej klasy Java musimy nazwać naszą metodę „onClick”, ale za pomocą atrybutu XML możemy dodać dowolną nazwę, co znacznie poprawi czytelność naszego kodu.
  • Podczas dodawania API poziomu 4 Google dodał atrybut „onClick” Xml, co oznacza, że ​​jest to nieco bardziej nowoczesna składnia, a nowoczesna składnia jest prawie zawsze lepsza.

Oczywiście nie zawsze można użyć atrybutu Xml, oto powody, dla których nie wybraliśmy go:

  • Jeśli pracujemy z fragmentami. Atrybut onClick można dodać tylko do działania, więc jeśli mamy fragment, musielibyśmy użyć anonimowej klasy.
  • Jeśli chcielibyśmy przenieść nasłuchiwanie onClick do osobnej klasy (może jeśli jest to bardzo skomplikowane i / lub chcielibyśmy użyć go ponownie w różnych częściach naszej aplikacji), nie chcielibyśmy używać atrybutu xml zarówno.
Bestin John
źródło
Należy pamiętać, że funkcja wywoływana za pomocą atrybutów XML powinna być zawsze publiczna, a jeśli zostaną zadeklarowane jako prywatne kury, spowoduje to wyjątek.
Bestin John
5

W Javie 8 prawdopodobnie możesz użyć Method Reference, aby osiągnąć to, czego chcesz.

Załóżmy, że jest to moduł onClickobsługi zdarzenia dla przycisku.

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

Następnie przekazujesz onMyButtonClickedodwołanie do metody instancji w takim setOnClickListener()wywołaniu.

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

Pozwoli ci to uniknąć bezpośredniego definiowania anonimowej klasy. Muszę jednak podkreślić, że metoda Reference w Javie 8 to tak naprawdę tylko cukier składniowy. W rzeczywistości tworzy on dla ciebie instancję anonimowej klasy (tak jak miało to miejsce w wyrażeniu lambda), stąd podobna ostrożność, jak w przypadku wyrejestrowywania programu obsługi zdarzeń zastosowano procedurę obsługi zdarzeń w stylu wyrażenia lambda. Ten artykuł wyjaśnia, że ​​to naprawdę miłe.

PS. Dla tych, którzy ciekawi, jak naprawdę mogę korzystać z funkcji języka Java 8 w Androidzie, jest to dzięki uprzejmości biblioteki retrolambda .

onelaview
źródło
5

Używając atrybutu XML, wystarczy zdefiniować metodę zamiast klasy, więc zastanawiałem się, czy to samo można zrobić za pomocą kodu, a nie w układzie XML.

Tak, możesz zrobić swoje fragmentlub activitywdrożyćView.OnClickListener

a kiedy inicjujesz swoje nowe obiekty widoku w kodzie, możesz to po prostu zrobić mView.setOnClickListener(this);

i to automatycznie ustawia wszystkie obiekty widoku w kodzie, aby używały onClick(View v)metody, którą posiada twój fragmentlub activityetc.

aby odróżnić widok, który wywołał onClickmetodę, można użyć instrukcji switch w v.getId()metodzie.

Ta odpowiedź różni się od tej, która mówi „Nie, to niemożliwe za pomocą kodu”

CQM
źródło
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
źródło
3

Wspierając odpowiedź Ruivo, tak, musisz zadeklarować metodę jako „publiczną”, aby móc korzystać z kliknięcia XML w Androidzie - Rozwijam aplikację kierującą z poziomu API od 8 (minSdk ...) do 16 (targetSdk ...).

Oświadczyłem, że moja metoda jest prywatna i spowodowała błąd, po prostu ogłaszając, że jest świetna.

Waqas Hasan
źródło
wydaje się, że zmienne zadeklarowane w klasie Hostingu nie mogą być użyte w zakresie deklarowanego wywołania zwrotnego; Pakiet Activity.mBundle zgłosi wyjątek IllegalStateException / NullPointerException, jeśli zostanie użyty w myFancyMethod ().
Quasaur
2

Uważaj, chociaż android:onClickXML wydaje się być wygodnym sposobem obsługi kliknięcia, setOnClickListenerimplementacja robi coś więcej niż dodanie onClickListener. Rzeczywiście, ustawiła właściwość view clickablena true.

Chociaż nie jest to problemem w większości implementacji Androida, zgodnie z konstruktorem telefonu, przycisk jest zawsze domyślnie ustawiony na clickable = true, ale inne konstruktory w niektórych modelach telefonów mogą mieć domyślną klikalną = false w widokach innych niż Button.

Więc ustawienie XML nie wystarcza, musisz cały czas zastanawiać się nad dodaniem android:clickable="true"przycisku innego niż przycisk, a jeśli masz urządzenie, w którym domyślna jest klikalna = prawda i zapomnisz choć raz umieścić ten atrybut XML, nie zauważysz problem w czasie wykonywania, ale otrzyma informacje zwrotne na rynku, kiedy będą w rękach Twoich klientów!

Ponadto nigdy nie możemy być pewni, w jaki sposób proguard zaciemni i zmieni nazwy atrybutów XML i metody klasy, więc nie jest w 100% bezpieczny, że pewnego dnia nigdy nie będą mieli błędu.

Więc jeśli nigdy nie chcesz mieć kłopotów i nigdy o tym nie myśleć, lepiej jest użyć setOnClickListenerbibliotek takich jak ButterKnife z adnotacjami@OnClick(R.id.button)

Livio
źródło
onClickAtrybut XML określa również clickable = truedlatego, że zwraca setOnClickListenerna Viewwewnętrznie
Florian Walther
1

Załóżmy, że chcesz dodać zdarzenie kliknięcia w ten sposób 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"/>

W pliku Java musisz napisać metodę taką jak ta.

public void register(View view) {
}
nazrul islam
źródło
0

Jestem Napisz ten kod w pliku 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"/>

I napisz ten kod we fragmencie ...

public void register(View view) {
}
użytkownik2786249
źródło
Jest to możliwe fragmentarycznie.
user2786249,
0

Najlepszym sposobem na to jest następujący kod:

 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
źródło
Nie rozumiem, jak to odpowiada na pytanie - pytający chce uniknąć tworzenia anonimowej klasy i zamiast tego użyć metody klasy.
ajshort
0

Aby ułatwić Ci życie i uniknąć Anonimowej klasy w setOnClicklistener (), zaimplementuj interfejs View.OnClicklistener w następujący sposób:

klasa publiczna YourClass rozszerza CommonActivity implementuje View.OnClickListener, ...

pozwala to uniknąć:

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

i idzie bezpośrednio do:

@Override
public void onClick(View v) {
  switch (v.getId()) {
    case R.id.your_view:
      yourMethod();
      break;
  }
}
Vicente Domingos
źródło