Cómo manejar los clics del botón usando el XML onClick dentro de los Fragmentos

Pre-Honeycomb (Android 3), cada actividad se registró para gestionar los clics de botón a través de la etiqueta onClick en un XML de diseño:

 android:onClick="myClickMethod" 

Dentro de ese método puede usar view.getId() y una instrucción switch para hacer la lógica del botón.

Con la presentación de Honeycomb, estoy dividiendo estas actividades en fragmentos que pueden reutilizarse dentro de muchas actividades diferentes. La mayoría del comportamiento de los botones es independiente de la actividad, y me gustaría que el código resida dentro del archivo de Fragmentos sin utilizar el método anterior (pre 1.6) de registrar el OnClickListener para cada botón.

 final Button button = (Button) findViewById(R.id.button_id); button.setOnClickListener(new View.OnClickListener() { public void onClick(View v) { // Perform action on click } }); 

El problema es que cuando mi diseño está inflado, sigue siendo la Actividad de alojamiento que recibe los clics del botón, no los Fragmentos individuales. ¿Hay un buen enfoque para cualquiera

  • Registre el fragmento para recibir los clics del botón?
  • ¿Pasar los eventos de clic de la Actividad al fragmento al que pertenecen?

Podrías hacer esto:

Actividad:

 Fragment someFragment; //...onCreate etc instantiating your fragments public void myClickMethod(View v) { someFragment.myClickMethod(v); } 

Fragmento:

 public void myClickMethod(View v) { switch(v.getId()) { // Just like you were doing } } 

En respuesta a @Ameen que quería menos acoplamiento por lo que los fragmentos son reutilizables

Interfaz:

 public interface XmlClickable { void myClickMethod(View v); } 

Actividad:

 XmlClickable someFragment; //...onCreate, etc. instantiating your fragments casting to your interface. 
 public void myClickMethod(View v) { someFragment.myClickMethod(v); } 

Fragmento:

 public class SomeFragment implements XmlClickable { //...onCreateView, etc. @Override public void myClickMethod(View v) { switch(v.getId()){ // Just like you were doing } } 

Prefiero usar la siguiente solución para manejar eventos onClick. Esto también funciona para Actividad y Fragmentos.

 public class StartFragment extends Fragment implements OnClickListener{ @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View v = inflater.inflate(R.layout.fragment_start, container, false); Button b = (Button) v.findViewById(R.id.StartButton); b.setOnClickListener(this); return v; } @Override public void onClick(View v) { switch (v.getId()) { case R.id.StartButton: ... break; } } } 

El problema, creo, es que la vista sigue siendo la actividad, no el fragmento. Los fragmentos no tienen una vista independiente propia y se adjuntan a la vista de actividades principales. Es por eso que el evento termina en la actividad, no en el fragmento. Es desafortunado, pero creo que necesitarás algún código para que esto funcione.

Lo que he estado haciendo durante las conversiones es simplemente agregar un oyente de clic que llama al antiguo manejador de eventos.

por ejemplo:

 final Button loginButton = (Button) view.findViewById(R.id.loginButton); loginButton.setOnClickListener(new OnClickListener() { @Override public void onClick(final View v) { onLoginClicked(v); } }); 

Recientemente resolví este problema sin tener que agregar un método a la Actividad de contexto o tener que implementar OnClickListener. No estoy seguro de si es una solución “válida” tampoco, pero funciona.

Basado en: https://developer.android.com/tools/data-binding/guide.html#binding_events

Se puede hacer con enlaces de datos: simplemente agregue su instancia de fragmento como una variable, luego puede vincular cualquier método con onClick.

         

Y el código de enlace del fragmento sería …

 public class CustomFragment extends Fragment { ... @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Inflate the layout for this fragment View view = inflater.inflate(R.layout.fragment_person_profile, container, false); FragmentCustomBinding binding = DataBindingUtil.bind(view); binding.setFragment(this); return view; } ... } 

ButterKnife es probablemente la mejor solución para el problema del desorden. Utiliza procesadores de anotación para generar el llamado código repetitivo de “método antiguo”.

Pero el método onClick todavía se puede usar, con un inflador personalizado.

Cómo utilizar

 @Override public View onCreateView(LayoutInflater inflater, ViewGroup cnt, Bundle state) { inflater = FragmentInflatorFactory.inflatorFor(inflater, this); return inflater.inflate(R.layout.fragment_main, cnt, false); } 

Implementación

 public class FragmentInflatorFactory implements LayoutInflater.Factory { private static final int[] sWantedAttrs = { android.R.attr.onClick }; private static final Method sOnCreateViewMethod; static { // We could duplicate its functionallity.. or just ignore its a protected method. try { Method method = LayoutInflater.class.getDeclaredMethod( "onCreateView", String.class, AttributeSet.class); method.setAccessible(true); sOnCreateViewMethod = method; } catch (NoSuchMethodException e) { // Public API: Should not happen. throw new RuntimeException(e); } } private final LayoutInflater mInflator; private final Object mFragment; public FragmentInflatorFactory(LayoutInflater delegate, Object fragment) { if (delegate == null || fragment == null) { throw new NullPointerException(); } mInflator = delegate; mFragment = fragment; } public static LayoutInflater inflatorFor(LayoutInflater original, Object fragment) { LayoutInflater inflator = original.cloneInContext(original.getContext()); FragmentInflatorFactory factory = new FragmentInflatorFactory(inflator, fragment); inflator.setFactory(factory); return inflator; } @Override public View onCreateView(String name, Context context, AttributeSet attrs) { if ("fragment".equals(name)) { // Let the Activity ("private factory") handle it return null; } View view = null; if (name.indexOf('.') == -1) { try { view = (View) sOnCreateViewMethod.invoke(mInflator, name, attrs); } catch (IllegalAccessException e) { throw new AssertionError(e); } catch (InvocationTargetException e) { if (e.getCause() instanceof ClassNotFoundException) { return null; } throw new RuntimeException(e); } } else { try { view = mInflator.createView(name, null, attrs); } catch (ClassNotFoundException e) { return null; } } TypedArray a = context.obtainStyledAttributes(attrs, sWantedAttrs); String methodName = a.getString(0); a.recycle(); if (methodName != null) { view.setOnClickListener(new FragmentClickListener(mFragment, methodName)); } return view; } private static class FragmentClickListener implements OnClickListener { private final Object mFragment; private final String mMethodName; private Method mMethod; public FragmentClickListener(Object fragment, String methodName) { mFragment = fragment; mMethodName = methodName; } @Override public void onClick(View v) { if (mMethod == null) { Class clazz = mFragment.getClass(); try { mMethod = clazz.getMethod(mMethodName, View.class); } catch (NoSuchMethodException e) { throw new IllegalStateException( "Cannot find public method " + mMethodName + "(View) on " + clazz + " for onClick"); } } try { mMethod.invoke(mFragment, v); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new AssertionError(e); } } } } 

Preferiría utilizar el manejo de clics en el código que usar el atributo onClick en XML al trabajar con fragmentos.

Esto se vuelve aún más fácil al migrar sus actividades a fragmentos. Simplemente puede llamar al manejador de clics (previamente configurado a android:onClick en XML) directamente desde cada bloque de case .

 findViewById(R.id.button_login).setOnClickListener(clickListener); ... OnClickListener clickListener = new OnClickListener() { @Override public void onClick(final View v) { switch(v.getId()) { case R.id.button_login: // Which is supposed to be called automatically in your // activity, which has now changed to a fragment. onLoginClick(v); break; case R.id.button_logout: ... } } } 

Cuando se trata de manejar clics en fragmentos, me parece más sencillo que android:onClick .

Esta es otra forma:

1. Crea un BaseFragment como este:

 public abstract class BaseFragment extends Fragment implements OnClickListener 

2.Utilizar

 public class FragmentA extends BaseFragment 

en lugar de

 public class FragmentA extends Fragment 

3. En tu actividad:

 public class MainActivity extends ActionBarActivity implements OnClickListener 

y

 BaseFragment fragment = new FragmentA; public void onClick(View v){ fragment.onClick(v); } 

Espero eso ayude.

Puede definir una callback como un atributo de su diseño XML. El artículo Atributos XML personalizados para sus widgets Android personalizados le mostrará cómo hacerlo para un widget personalizado. El crédito va para Kevin Dion 🙂

Estoy investigando si puedo agregar atributos de estilo a la clase Fragmento base.

La idea básica es tener la misma funcionalidad que View implementa cuando se trata de la callback onClick.

En mi caso de uso, tengo 50 ImageViews impares que necesitaba enganchar en un solo método onClick. Mi solución es recorrer las vistas dentro del fragmento y establecer el mismo escucha onclick en cada uno:

  final View.OnClickListener imageOnClickListener = new View.OnClickListener() { @Override public void onClick(View v) { chosenImage = ((ImageButton)v).getDrawable(); } }; ViewGroup root = (ViewGroup) getView().findViewById(R.id.imagesParentView); int childViewCount = root.getChildCount(); for (int i=0; i < childViewCount; i++){ View image = root.getChildAt(i); if (image instanceof ImageButton) { ((ImageButton)image).setOnClickListener(imageOnClickListener); } } 

Añadiendo a la respuesta de Blundell,
Si tiene más fragmentos, con un montón de onClicks:

Actividad:

 Fragment someFragment1 = (Fragment)getFragmentManager().findFragmentByTag("someFragment1 "); Fragment someFragment2 = (Fragment)getFragmentManager().findFragmentByTag("someFragment2 "); Fragment someFragment3 = (Fragment)getFragmentManager().findFragmentByTag("someFragment3 "); ...onCreate etc instantiating your fragments public void myClickMethod(View v){ if (someFragment1.isVisible()) { someFragment1.myClickMethod(v); }else if(someFragment2.isVisible()){ someFragment2.myClickMethod(v); }else if(someFragment3.isVisible()){ someFragment3.myClickMethod(v); } } 

En tu fragmento:

  public void myClickMethod(View v){ switch(v.getid()){ // Just like you were doing } } 

Si se registra en xml usando android: Onclick = “”, la callback se dará a la actividad respetada en cuyo contexto pertenece su fragmento (getActivity ()). Si dicho método no se encuentra en la Actividad, el sistema emitirá una excepción.

Es posible que desee considerar el uso de EventBus para eventos desacoplados. Puede escuchar eventos muy fácilmente. También puede asegurarse de que el evento se esté recibiendo en el hilo de la interfaz de usuario (en lugar de llamar a runOnUiThread … para usted mismo para cada suscripción de evento)

https://github.com/greenrobot/EventBus

de Github:

Bus de eventos optimizado para Android que simplifica la comunicación entre Actividades, Fragmentos, Hilos, Servicios, etc. Menos código, mejor calidad

Cuando veo respuestas, son de alguna manera viejos. Recientemente Google introdujo DataBinding, que es mucho más fácil de manejar onClick o asignar en su xml.

Aquí hay un buen ejemplo en el que puede ver cómo manejar esto:

            

También hay un buen tutorial sobre DataBinding que puedes encontrar aquí .

Me gustaría agregar a la respuesta de Adjorn Linkz.

Si necesita múltiples controladores, puede usar referencias lambda

 void onViewCreated(View view, Bundle savedInstanceState) { view.setOnClickListener(this::handler); } void handler(View v) { ... } 

El truco aquí es que la firma del método del handler coincide View.OnClickListener.onClick firma View.OnClickListener.onClick . De esta manera, no necesitará la interfaz View.OnClickListener .

Además, no necesitará ninguna instrucción de cambio.

Lamentablemente, este método solo se limita a las interfaces que requieren un único método o una lambda.

Esto ha funcionado para mí: (Android studio)

  @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.update_credential, container, false); Button bt_login = (Button) rootView.findViewById(R.id.btnSend); bt_login.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { System.out.println("Hi its me"); }// end onClick }); return rootView; }// end onCreateView 

La mejor solución en mi humilde opinión:

en fragmento:

 protected void addClick(int id) { try { getView().findViewById(id).setOnClickListener(this); } catch (Exception e) { e.printStackTrace(); } } public void onClick(View v) { if (v.getId()==R.id.myButton) { onMyButtonClick(v); } } 

luego en Fragment’s onViewStateRestored:

 addClick(R.id.myButton); 

Tu actividad está recibiendo la callback como debe haber usado:

 mViewPagerCloth.setOnClickListener((YourActivityName)getActivity()); 

Si quieres que tu fragmento reciba una callback, haz esto:

 mViewPagerCloth.setOnClickListener(this); 

e implementar la interfaz onClickListener en Fragment