¿En qué se diferencia exactamente el atributo android: onClick XML del setOnClickListener?

416

De lo que he leído, puede asignar un onClickcontrolador a un botón de dos maneras.

Usando el android:onClickatributo XML donde solo usa el nombre de un método público con la firma void name(View v)o usando el setOnClickListenermétodo donde pasa un objeto que implementa la OnClickListenerinterfaz. Esto último a menudo requiere una clase anónima que personalmente no me gusta (gusto personal) o definir una clase interna que implemente el OnClickListener.

Al usar el atributo XML, solo necesita definir un método en lugar de una clase, por lo que me preguntaba si lo mismo se puede hacer a través del código y no en el diseño XML.

emitrax
fuente
44
Leí tu problema y creo que estás atrapado en el mismo lugar como yo. Encontré un video muy bueno que me ayudó mucho a resolver mi problema. Encuentra el video en el siguiente enlace: youtube.com/watch?v=MtmHURWKCmg&feature=youtu.be Espero que esto también te ayude :)
user2166292
99
Para aquellos que desean ahorrar tiempo viendo el video publicado en el comentario anterior, simplemente demuestra cómo dos botones pueden tener el mismo método para su onClickatributo en el archivo de diseño. Esto se hace gracias al parámetro View v. Simplemente verifica if (v == findViewById(R.id.button1)) etc.
CodyBugstein
13
@ Imray, creo que es mejor usarlo v.getId() == R.id.button1, ya que no tiene que encontrar el control real y hacer una comparación. Y puede usar un en switchlugar de muchos ifs.
Sami Kuhmonen
3
Este tutorial te ayudará mucho. haga clic aquí
c49
Usar xml android: onClick provoca un bloqueo.

Respuestas:

604

No, eso no es posible a través del código. Android solo implementa el OnClickListenerpara usted cuando define el android:onClick="someMethod"atributo.

Esos dos fragmentos de código son iguales, solo implementados de dos maneras diferentes.

Implementación de código

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
}

Arriba hay una implementación de código de un OnClickListener. Y esta es la implementación XML.

Implementación 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 segundo plano, Android no hace nada más que el código Java, llamando a su método en un evento de clic.

Tenga en cuenta que con el XML anterior, Android buscará el onClickmétodo myFancyMethod()solo en la Actividad actual. Es importante recordar esto si está utilizando fragmentos, ya que incluso si agrega el XML anterior utilizando un fragmento, Android no buscará el onClickmétodo en el .javaarchivo del fragmento utilizado para agregar el XML.

Otra cosa importante que noté. Mencionaste que no prefieres métodos anónimos . Querías decir que no te gustan las clases anónimas .

Octavian A. Damiean
fuente
44
No soy un gurú de Java, pero sí, me refería a una clase anónima. Gracias por su respuesta. Muy claro.
emitrax
118
Tenga en cuenta que si usa el onclick XML, debe colocar el método onclick ( myFancyMethod()) en la Actividad actual. Esto es importante si está utilizando fragmentos, ya que la forma programática de configurar los oyentes onclick probablemente tendrá el método que maneja los clics en onCreateView () de un fragmento ... donde no se encontrará si se hace referencia desde XML.
Peter Ajtai
12
Sí, el método tiene que ser público.
Octavian A. Damiean
12
Lo interesante es que hacerlo en código permite proteger el acceso al método al hacer que el método sea privado, mientras que hacerlo de forma xml conduce a la exposición del método.
bgse
55
El hecho de que la función (en el enfoque XML) debe estar en Actividad es importante no solo cuando se consideran fragmentos, sino también vistas personalizadas (que contienen el botón). Cuando tiene una vista personalizada que reutiliza en múltiples actividades, pero desea utilizar el mismo método onClick para todos los casos, el método XML no es el más conveniente. Debería poner esto en ClickMethod (con el mismo cuerpo) en cada actividad que use su vista personalizada.
Bartek Lipinski
87

Cuando vi la respuesta principal, me di cuenta de que mi problema no era poner el parámetro (Ver v) en el método elegante:

public void myFancyMethod(View v) {}

Al intentar acceder desde el xml, uno debe usar

android:onClick="myFancyMethod"/>

Espero que ayude a alguien.

jp093121
fuente
74

android:onClick es para API nivel 4 en adelante, por lo que si está apuntando a <1.6, entonces no puede usarlo.

James
fuente
33

¡Comprueba si olvidaste hacer público el método!

Ruivo
fuente
1
¿Por qué es obligatorio hacerlo público?
eRaisedToX
3
@eRaisedToX Creo que está bastante claro: si no es público, no se puede llamar desde el marco de Android.
m0skit0
28

La especificación de los android:onClickresultados del atributo en la Buttoninstancia llama setOnClickListenerinternamente. Por lo tanto, no hay absolutamente ninguna diferencia.

Para tener una comprensión clara, veamos cómo onClickel marco maneja el atributo XML .

Cuando se infla un archivo de diseño, se instancian todas las vistas especificadas en él. En este caso específico, la Buttoninstancia se crea utilizando el public Button (Context context, AttributeSet attrs, int defStyle)constructor. Todos los atributos en la etiqueta XML se leen del paquete de recursos y se pasan AttributeSetal constructor.

ButtonLa clase se hereda de la Viewclase View, lo que hace que se llame al constructor, que se encarga de configurar el controlador de devolución de llamada a través de setOnClickListener.

El atributo onClick definido en attrs.xml se denomina en View.java como R.styleable.View_onClick.

Aquí está el código View.javaque hace la mayor parte del trabajo por usted llamando setOnClickListenersolo.

 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;

Como puede ver, setOnClickListenerse llama para registrar la devolución de llamada, como lo hacemos en nuestro código. La única diferencia es que se utiliza Java Reflectionpara invocar el método de devolución de llamada definido en nuestra Actividad.

Aquí están las razones de los problemas mencionados en otras respuestas:

  • El método de devolución de llamada debe ser público : dado que Java Class getMethodse utiliza, solo se buscan funciones con especificador de acceso público. De lo contrario, esté listo para manejar la IllegalAccessExceptionexcepción.
  • Al usar Button con onClick in Fragment, la devolución de llamada debe definirse en Activity : getContext().getClass().getMethod()call restringe la búsqueda del método al contexto actual, que es Activity en caso de Fragment. Por lo tanto, el método se busca dentro de la clase Actividad y no en la clase Fragmento.
  • El método de devolución de llamada debe aceptar el parámetro Ver : ya que Java Class getMethodbusca el método que acepta View.classcomo parámetro.
Manish Mulimani
fuente
1
Esa fue la pieza que faltaba para mí: Java usa Reflection para encontrar el controlador de clic que comienza con getContext (). Para mí fue un poco misterioso cómo el clic se propaga de un fragmento a una Actividad.
Andrew Queisser
15

Aquí hay muy buenas respuestas, pero quiero agregar una línea:

En android:onclickXML, Android usa la reflexión de Java detrás de escena para manejar esto.

Y como se explica aquí, la reflexión siempre ralentiza el rendimiento. (especialmente en Dalvik VM). Registrarse onClickListeneres una mejor manera.

Krupal Shah
fuente
55
¿Cuánto puede ralentizar la aplicación? :) Medio milisegundo; ¿ni siquiera? En comparación con realmente inflar el diseño, es como una pluma y una ballena
Konrad Morawski, el
14

Tenga en cuenta que si desea utilizar la función onClick XML, el método correspondiente debe tener un parámetro, cuyo tipo debe coincidir con el objeto XML.

Por ejemplo, un botón estará vinculado a su método a través de su cadena de nombre: android:onClick="MyFancyMethod"pero la declaración del método debe mostrar: ...MyFancyMethod(View v) {...

Si está intentando agregar esta característica a un elemento del menú , tendrá la misma sintaxis exacta en el archivo XML, pero su método se declarará como:...MyFancyMethod(MenuItem mi) {...

Antoine Lizée
fuente
6

Otra forma de configurar sus oyentes al hacer clic sería utilizar XML. Simplemente agregue el atributo android: onClick a su etiqueta.

Es una buena práctica utilizar el atributo xml "onClick" sobre una clase Java anónima siempre que sea posible.

En primer lugar, echemos un vistazo a la diferencia en el código:

Atributo XML / atributo onClick

Porción XML

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

Porción de Java

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

Clase anónima de Java / setOnClickListener

Porción XML

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

Porción de Java

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

Estos son los beneficios de usar el atributo XML sobre una clase anónima de Java:

  • Con la clase Java anónima siempre tenemos que especificar una identificación para nuestros elementos, pero con la identificación del atributo XML se puede omitir.
  • Con la clase Anonymous Java tenemos que buscar activamente el elemento dentro de la vista (parte de findViewById), pero con el atributo XML, Android lo hace por nosotros.
  • La clase Java anónima requiere al menos 5 líneas de código, como podemos ver, pero con el atributo XML 3 líneas de código son suficientes.
  • Con la clase Anonymous Java tenemos que nombrar nuestro método "onClick", pero con el atributo XML podemos agregar cualquier nombre que queramos, lo que ayudará dramáticamente con la legibilidad de nuestro código.
  • Google agregó el atributo "onClick" Xml durante el lanzamiento del nivel 4 de la API, lo que significa que es una sintaxis un poco más moderna y la sintaxis moderna es casi siempre mejor.

Por supuesto, no siempre es posible usar el atributo Xml, estas son las razones por las que no lo elegiríamos:

  • Si estamos trabajando con fragmentos. El atributo onClick solo se puede agregar a una actividad, por lo que si tenemos un fragmento, tendremos que usar una clase anónima.
  • Si quisiéramos mover el oyente onClick a una clase separada (tal vez si es muy complicado y / o nos gustaría reutilizarlo en diferentes partes de nuestra aplicación), entonces no querríamos usar el atributo xml ya sea.
Bestin John
fuente
Y tenga en cuenta que la función llamada utilizando atributos XML siempre debe ser pública y si se declaran como gallinas privadas resultará en una excepción.
Bestin John
5

Con Java 8, probablemente podría usar la Referencia de métodos para lograr lo que desea.

Suponga que este es su onClickcontrolador de eventos para un botón.

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

Luego, pasa la onMyButtonClickedreferencia del método de instancia en una setOnClickListener()llamada como esta.

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

Esto le permitirá evitar definir explícitamente una clase anónima usted mismo. Sin embargo, debo enfatizar que la Referencia de métodos de Java 8 es en realidad solo un azúcar sintáctico. En realidad, crea una instancia de la clase anónima para usted (al igual que lo hizo la expresión lambda), por lo tanto, se debe tener precaución similar cuando se aplica el controlador de eventos de estilo de expresión lambda cuando se anula el registro de su controlador de eventos. Este artículo lo explica realmente bien.

PD. Para aquellos que tienen curiosidad acerca de cómo puedo usar realmente la función de lenguaje Java 8 en Android, es una cortesía de la biblioteca retrolambda .

onelaview
fuente
5

Al usar el atributo XML, solo necesita definir un método en lugar de una clase, por lo que me preguntaba si lo mismo se puede hacer a través del código y no en el diseño XML.

Sí, puedes hacer tu fragmento activityimplementarView.OnClickListener

y cuando inicializa sus nuevos objetos de vista en código, simplemente puede hacer mView.setOnClickListener(this);

y esto establece automáticamente todos los objetos en vista de código para utilizar el onClick(View v)método que tu fragmento activityetc tiene.

Para distinguir qué vista ha llamado al onClickmétodo, puede usar una instrucción de cambio en el v.getId()método.

Esta respuesta es diferente de la que dice "No, eso no es posible a través del código"

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

Respaldando la respuesta de Ruivo, sí, tiene que declarar el método como "público" para poder usarlo en el onclick XML de Android: estoy desarrollando una aplicación dirigida desde el nivel de API 8 (minSdk ...) a 16 (targetSdk ...).

Estaba declarando mi método como privado y causó un error, solo declarándolo como público funciona muy bien.

Waqas Hasan
fuente
parece que las variables declaradas en la clase de Actividad de alojamiento no pueden usarse en el ámbito de la devolución de llamada declarada; Bundle Activity.mBundle arrojará una IllegalStateException / NullPointerException si se usa en myFancyMethod ().
Quasaur
2

Tenga cuidado, aunque android:onClickXML parece ser una forma conveniente de manejar el clic, la setOnClickListenerimplementación hace algo más que agregar el onClickListener. De hecho, puso la propiedad de vista clickableen verdadero.

Si bien puede no ser un problema en la mayoría de las implementaciones de Android, de acuerdo con el constructor del teléfono, el botón siempre es predeterminado en clickable = true, pero otros constructores en algunos modelos de teléfono pueden tener un clickable = false predeterminado en las vistas que no son Button.

Por lo tanto, establecer el XML no es suficiente, debe pensar todo el tiempo para agregar android:clickable="true"un botón que no sea, y si tiene un dispositivo donde el valor predeterminado es clicable = verdadero y olvida incluso una vez poner este atributo XML, no notará El problema en tiempo de ejecución, pero recibirá la retroalimentación en el mercado cuando estará en manos de sus clientes.

Además, nunca podemos estar seguros de cómo Proguard ofuscará y cambiará el nombre de los atributos XML y el método de clase, por lo que no es 100% seguro que nunca tendrán un error algún día.

Entonces, si nunca quieres tener problemas y nunca piensas en ello, es mejor usar setOnClickListenerbibliotecas como ButterKnife con anotaciones@OnClick(R.id.button)

Livio
fuente
El onClickatributo XML también establece clickable = trueporque llama setOnClickListeneren el Viewinterior
Florian Walther
1

Supongamos que desea agregar un evento de clic como este 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"/>

En el archivo java, debe escribir un método como este.

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

Estoy Escribe este código en el archivo 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"/>

Y escribe este código en fragmento ...

public void register(View view) {
}
usuario2786249
fuente
Esto es posible en fragmentos.
user2786249
0

La mejor manera de hacerlo es con el siguiente código:

 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
fuente
No veo cómo esto responde la pregunta: el autor de la pregunta quiere evitar crear una clase anónima y, en cambio, usar un método de clase.
ajshort
0

Para facilitarle la vida y evitar la clase anónima en setOnClicklistener (), implemente una interfaz View.OnClicklistener como se muestra a continuación:

public class YourClass extiende CommonActivity implementa View.OnClickListener, ...

esto evita:

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

y va directamente a:

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