¿Cómo difiere exactamente el atributo android: onClick XML de setOnClickListener?

Por lo que he leído, puede asignar un controlador onClick a un botón de dos maneras.

Utilizando el atributo android:onClick XML donde solo utiliza el nombre de un método público con el void name(View v) la firma void name(View v) o mediante el método setOnClickListener donde pasa un objeto que implementa la interfaz OnClickListener . Este último a menudo requiere una clase anónima que personalmente no me gusta (gusto personal) o definir una clase interna que implementa OnClickListener .

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

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

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

Implementación del 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 está una implementación de código de OnClickListener . Y esta es la implementación de XML.

Implementación XML

     

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 método onClick 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 método onClick en el archivo .java del fragmento utilizado para agregar el XML.

Otra cosa importante que noté. Usted mencionó que no prefiere los métodos anónimos. Querías decir que no te gustan las clases anónimas.

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

 public void myFancyMethod(View v) {} 

Al intentar acceder desde el xml, uno debe usar

 android:onClick="myFancyMethod"/> 

Espero que ayude a alguien.

android:onClick es para el nivel 4 de la API en adelante, por lo que si tiene como objective <1.6, entonces no puede usarlo.

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

Especificar el atributo android:onClick da como resultado una instancia de Button llama a setOnClickListener internamente. Por lo tanto, no hay absolutamente ninguna diferencia.

Para tener un entendimiento claro, veamos cómo el framework maneja el atributo XML onClick .

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

Button clase Button se hereda de la clase View , lo que da como resultado que se llame al constructor View , que se encarga de configurar el controlador de callback mediante setOnClickListener .

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

Aquí está el código de View.java que hace la mayor parte del trabajo llamando a setOnClickListener por sí mismo.

  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, se llama a setOnClickListener para registrar la callback, como lo hacemos en nuestro código. La única diferencia es que utiliza Java Reflection para invocar el método de callback definido en nuestra actividad.

Estos son los motivos de los problemas mencionados en otras respuestas:

  • El método de callback debe ser público : desde que se usa Java Class getMethod , solo se buscan las funciones con el especificador de acceso público. De lo contrario, prepárese para manejar la excepción IllegalAccessException .
  • Al usar Button con onClick en Fragment, la callback debe definirse en Activity : getContext().getClass().getMethod() call restringe la búsqueda de métodos al contexto actual, que es Activity en caso de Fragment. Por lo tanto, el método se busca dentro de la clase Activity y no en la clase Fragment.
  • El método de callback debe aceptar el parámetro Ver : desde Java Class getMethod busca el método que acepta View.class como parámetro.

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 se vinculará a su método a través de su cadena de nombre: android:onClick="MyFancyMethod" pero la statement del método debería mostrar: ...MyFancyMethod(View v) {...

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

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

En android:onclick en XML, Android usa el reflection de Java detrás de la escena para manejar esto.

Y como se explica aquí, la reflexión siempre ralentiza el rendimiento. (especialmente en Dhalvik VM). El registro de onClickListener es una mejor manera.

Con Java 8, probablemente puedas usar Method Reference para lograr lo que quieres.

Supongamos que es su controlador de eventos onClick para un botón.

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

Luego, pasa la referencia de método de instancia onMyButtonClicked en una setOnClickListener() a setOnClickListener() como esta.

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

Esto le permitirá evitar usted mismo la definición explícita de una clase anónima. Sin embargo, debo enfatizar que la Referencia de método 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 aplica una precaución similar a la del controlador de eventos lambda-expression-style cuando se elimina el registro de su controlador de eventos. Este artículo lo explica muy bien.

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

Otra forma de configurar sus escuchas en clic sería usar XML. Solo agrega el atributo android: onClick a tu etiqueta.

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

Antes que nada, echemos un vistazo a la diferencia en el código:

Atributo XML Attribute / onClick

Porción de XML

  

Porción de Java

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

Anónimo Java Class / setOnClickListener

Porción XML

  

Porción de Java

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

Aquí están los beneficios de usar el atributo XML sobre una clase Java anónima:

  • Con la clase Java anónima, siempre debemos especificar una identificación para nuestros elementos, pero con el atributo XML, el ID puede omitirse.
  • Con la clase Java anónima tenemos que buscar activamente el elemento dentro de la vista (porción 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 Java anónima tenemos que nombrar nuestro método “onClick”, pero con el atributo XML podemos agregar cualquier nombre que deseemos, lo que ayudará enormemente con la legibilidad de nuestro código.
  • El atributo Xml “onClick” fue agregado por Google durante la versión de nivel 4 de API, lo que significa que es una syntax un poco más moderna y la syntax moderna casi siempre es mejor.

Por supuesto, no siempre es posible usar el atributo Xml, aquí están las razones por las cuales no lo elegimos:

  • Si estamos trabajando con fragmentos El atributo onClick solo se puede agregar a una actividad, por lo que si tenemos un fragmento, tendríamos 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 volver a usarlo en diferentes partes de nuestra aplicación), entonces no querríamos usar el atributo xml ya sea.

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

Sí, puedes hacer que tu fragment o activity implemente View.OnClickListener

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

y esto establece automáticamente todos los objetos de vista en código para usar el método onClick(View v) que tiene su fragment o activity etc.

para distinguir qué vista ha llamado el método onClick , puede usar una instrucción switch en el método v.getId() .

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

Respaldo a la respuesta de Ruivo, sí, debes declarar el método como “público” para poder utilizar en onclick XML de Android. Estoy desarrollando una orientación de aplicación desde API Nivel 8 (minSdk …) a 16 (targetSdk …).

Estaba declarando mi método como privado y provocó un error, simplemente declararlo como público funciona muy bien.

  Add Button in xml and give onclick attribute name that is the name of Method.   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; }} 

Supongamos que desea agregar un evento de clic como este main.xml

  

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

 public void register(View view) { } 

Estoy escribiendo este código en un archivo xml …

  

Y escribe este código en fragmentos …

 public void register(View view) { } 

La mejor forma 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 } }); 

Tenga cuidado, aunque android:onClick XML parece ser una forma conveniente de manejar el clic, la implementación setOnClickListener hace algo más que agregar onClickListener . De hecho, puso la propiedad view clickable en true.

Si bien es posible que no sea 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 para hacer clic = verdadero, pero otros constructores en algún modelo de teléfono pueden tener un clic = falso predeterminado en vistas de botones.

Entonces, establecer el XML no es suficiente, tienes que pensar todo el tiempo para agregar android:clickable="true" en el botón no, y si tienes un dispositivo donde el valor predeterminado es clickable = true e incluso olvidas poner este XML atributo, no se dará cuenta del problema en tiempo de ejecución, pero recibirá los comentarios en el mercado cuando esté 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 de que nunca tendrán un error algún día.

Entonces, si nunca quieres tener problemas y nunca lo piensas, es mejor usar setOnClickListener o bibliotecas como ButterKnife con la anotación @OnClick(R.id.button)