studio - onclick android ejemplo
Cómo manejar los clics de botón usando el XML onClick dentro de fragmentos (17)
Antes de Honeycomb (Android 3), cada actividad se registró para manejar los clics de los botones a través de la etiqueta onClick
en el XML de un 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 introducción de Honeycomb, estoy dividiendo estas actividades en fragmentos que se pueden reutilizar en muchas actividades diferentes. La mayor parte del comportamiento de los botones es independiente de la actividad, y me gustaría que el código residiera dentro del archivo de Fragmentos sin usar el método antiguo (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 se inflan mis diseños, sigue siendo la Actividad de hospedaje la que recibe los clics del botón, no los Fragmentos individuales. ¿Hay un buen enfoque para cualquiera
- Registrar el fragmento para recibir los clics del botón?
- ¿Pasar los eventos de clic de la Actividad al fragmento al que pertenecen?
Agregando a la respuesta de Blundell,
Si tienes 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
}
}
Como veo respuestas son de alguna manera viejas. Recientemente, Google introdujo DataBinding que es mucho más fácil de manejar onClick o asignando en su xml.
Aquí hay un buen ejemplo que puedes ver cómo manejar esto:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable name="handlers" type="com.example.Handlers"/>
<variable name="user" type="com.example.User"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.firstName}"
android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
<TextView android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@{user.lastName}"
android:onClick="@{user.isFriend ? handlers.onClickFriend : handlers.onClickEnemy}"/>
</LinearLayout>
</layout>
También hay un tutorial muy bueno sobre DataBinding que puedes encontrar Here .
El problema que 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 principal. Es por eso que el evento termina en la Actividad, no en el fragmento. Es desafortunado, pero creo que necesitarás un código para hacer que esto funcione.
Lo que he estado haciendo durante las conversiones es simplemente agregar un detector de clics que llama al antiguo controlador 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);
}
});
En mi caso de uso, tengo 50 Visiones de imagen impares que necesitaba para conectar en un solo método onClick. Mi solución es recorrer las vistas dentro del fragmento y establecer el mismo oyente 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);
}
}
Es posible que desee considerar el uso de EventBus para eventos desacoplados. Puede escuchar eventos con mucha facilidad. También puede asegurarse de que el evento se reciba en el subproceso de la interfaz de usuario (en lugar de llamar a runOnUiThread para usted por cada suscripción de evento)
https://github.com/greenrobot/EventBus
desde Github:
Bus de eventos optimizado para Android que simplifica la comunicación entre Actividades, Fragmentos, Subprocesos, Servicios, etc. Menos código, mejor calidad
Esta es otra manera :
1.Crear un BaseFragment como este:
public abstract class BaseFragment extends Fragment implements OnClickListener
2.Uso
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.
Esto ha estado funcionando para mí: (estudio de Android)
@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
Me gustaría añadir a la answer Adjorn Linkz.
Si necesita varios manejadores, 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 solo método, o un lambda.
Mejor solución en mi humilde opinión:
en el 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);
Prefiero optar por el manejo de clics en el código que usar el atributo onClick
en XML cuando se trabaja con fragmentos.
Esto se vuelve aún más fácil al migrar sus actividades a fragmentos. Solo puede llamar al controlador de clic (previamente establecido en 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 simple que android:onClick
.
Prefiero usar la siguiente solución para manejar eventos onClick. Esto funciona también 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;
}
}
}
Puede definir una devolución de llamada como un atributo de su diseño XML. El artículo Atributos XML personalizados para sus widgets de Android personalizados le mostrará cómo hacerlo para un widget personalizado. El crédito es para Kevin Dion :)
Estoy investigando si puedo agregar atributos de estilo a la clase de Fragmento base.
La idea básica es tener la misma funcionalidad que implementa View cuando se trata con la devolución de llamada onClick.
Recientemente resolví este problema sin tener que agregar un método al contexto Actividad o tener que implementar OnClickListener. No estoy seguro 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.
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="com.example.testapp.fragments.CustomFragment">
<data>
<variable name="fragment" type="com.example.testapp.fragments.CustomFragment"/>
</data>
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_place_black_24dp"
android:onClick="@{fragment.buttonClicked}"/>
</LinearLayout>
</layout>
Y el código de vinculación de fragmentos 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;
}
...
}
Si se registra en xml con android: Onclick = "", se devolverá la llamada a la Actividad respetada en cuyo contexto pertenece su fragmento (getActivity ()). Si tal método no se encuentra en la Actividad, entonces el sistema lanzará una excepción.
Su actividad está recibiendo la devolución de llamada como debe haber utilizado:
mViewPagerCloth.setOnClickListener((YourActivityName)getActivity());
Si desea que su fragmento reciba una devolución de llamada, haga esto:
mViewPagerCloth.setOnClickListener(this);
e implementar la interfaz onClickListener
en Fragmento
Usted podría 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 para que los Fragmentos sean 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
}
}
ButterKnife es probablemente la mejor solución para el problema del desorden. Utiliza procesadores de anotación para generar el código de repetición denominado "método antiguo".
Pero el método onClick todavía se puede utilizar, 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);
}
}
}
}