Android: elementos ListView con varios botones en los que se puede hacer clic

182

Tengo un lugar ListViewdonde cada elemento de la lista contiene un TextView y dos botones diferentes. Algo como esto:

ListView
--------------------
[Text]
[Button 1][Button 2]
--------------------
[Text]
[Button 1][Button 2]
--------------------
... (and so on) ...

Con este código puedo crear un OnItemClickListenerpara todo el artículo:

listView.setOnItemClickListener(new OnItemClickListener() {
    @Override
    public void onItemClick(AdapterView<?> list, View view, int position, long id) {
        Log.i(TAG, "onListItemClick: " + position);

        }

    }
});

Sin embargo, no quiero que se pueda hacer clic en todo el elemento, sino solo los dos botones de cada elemento de la lista.

Entonces mi pregunta es, ¿cómo implemento un onClickListener para estos dos botones con los siguientes parámetros:

  • int button (en qué botón del elemento se ha hecho clic)
  • int position (que es el elemento en la lista en la que ocurrió el clic del botón)

Actualización: encontré una solución como se describe en mi respuesta a continuación. Ahora puedo hacer clic / tocar el botón a través de la pantalla táctil. Sin embargo, no puedo seleccionarlo manualmente con el trackball. Siempre selecciona todo el elemento de la lista y desde allí va directamente al siguiente elemento de la lista ignorando los botones, aunque configuré .setFocusable(true)y setClickable(true)para los botones getView().

También agregué este código a mi adaptador de lista personalizado:

@Override
public boolean  areAllItemsEnabled() {
    return false;           
}

@Override
public boolean isEnabled(int position) {
        return false;
}

Esto hace que ya no se pueda seleccionar ningún elemento de la lista. Pero no ayudó a hacer que los botones anidados fueran seleccionables.

Alguien una idea?

znq
fuente
¿Siguen siendo necesarios?
Stuart Axon
Si observa el código de BaseAdapter, verá que areAllItemsEnabled () y isEnabled () simplemente están codificados como verdaderos, lo que los convierte en simples marcadores de posición sin ninguna lógica.
Artem Russakovskii
¿Qué sucede si quiero usar un Adaptador de cursor simple? ¿Tengo que hacer que un adaptador personalizado extienda un cursoractor simple?
oratis

Respuestas:

150

La solución a esto es realmente más fácil de lo que pensaba. Simplemente puede agregar en el getView()método de su adaptador personalizado un setOnClickListener () para los botones que está utilizando.

Todos los datos asociados con el botón deben agregarse myButton.setTag()en el getView()y se puede acceder en onClickListener a través deview.getTag()

Publiqué una solución detallada en mi blog como tutorial.

znq
fuente
2
Fijo. Gracias por informar :-)
znq
¿Cómo exactamente obtuvo el curItem.url de la consulta, sería más específico?
oratis
1
Gracias znq, eso fue muy útil para mí ... Ahorré un montón de tiempo.
Nandagopal T
Mire esta charla a las 11:39 hay un excelente ejemplo: youtu.be/wDBM6wVEO70?t=11m39s Luego haga lo que @znq dice ... setTag () cuando convertView == null y getTag () en onClick () método del botón onClickListener (). ¡Gracias!
Shehaaz
12
Sería genial tener respuestas en SO y no apuntar a otra página. ¡El enlace del blog está muerto!
gsb
63

Esta es una especie de apéndice de la respuesta de @ znq ...

Hay muchos casos en los que desea conocer la posición de la fila para un elemento en el que se hizo clic Y desea saber qué vista de la fila se tocó. Esto va a ser mucho más importante en las IU de las tabletas.

Puede hacerlo con el siguiente adaptador personalizado:

private static class CustomCursorAdapter extends CursorAdapter {

    protected ListView mListView;

    protected static class RowViewHolder {
        public TextView mTitle;
        public TextView mText;
    }

    public CustomCursorAdapter(Activity activity) {
        super();
        mListView = activity.getListView();
    }

    @Override
    public void bindView(View view, Context context, Cursor cursor) {
        // do what you need to do
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {
        View view = View.inflate(context, R.layout.row_layout, null);

        RowViewHolder holder = new RowViewHolder();
        holder.mTitle = (TextView) view.findViewById(R.id.Title);
        holder.mText = (TextView) view.findViewById(R.id.Text);

        holder.mTitle.setOnClickListener(mOnTitleClickListener);
        holder.mText.setOnClickListener(mOnTextClickListener);

        view.setTag(holder);

        return view;
    }

    private OnClickListener mOnTitleClickListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            final int position = mListView.getPositionForView((View) v.getParent());
            Log.v(TAG, "Title clicked, row %d", position);
        }
    };

    private OnClickListener mOnTextClickListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            final int position = mListView.getPositionForView((View) v.getParent());
            Log.v(TAG, "Text clicked, row %d", position);
        }
    };
}
greg7gkb
fuente
66
o también puede usar algo como esto ListView mListView = (ListView) v.getParent (). getParent ();
max4ever
1
¿Cómo puedo obtener la posición si uso el adaptador en una clase separada? Simplemente utilicé la posición dentro de OnClickListener, algo así como like_c.get (position). Sugiere hacer la posición final en el método getView del adaptador. Si hago ese cambio, la posición que obtengo es la misma para todos los elementos de la vista. ¿Podría sugerirme cómo resolver esto?
Manikandan
Eso es porque cuando usa el parámetro de posición del método getView, le da la posición del elemento de la lista que se creó más recientemente pero no el que hizo clic
Archie.bpgc
@Manikandan: si está utilizando el método getView () del adaptador, entonces ya le ha pasado la posición en los parámetros del método. En ese caso, podría usar algo como setTag () para almacenar la información de posición.
greg7gkb
1
Estoy usando su código en mi aplicación, pero el evento de clic se llama después de un retraso o cuando intento desplazar la vista de lista. ¿Alguna idea de por qué está sucediendo esto?
Abdullah Umer
22

Para futuros lectores:

Para seleccionar manualmente los botones con el uso del trackball:

myListView.setItemsCanFocus(true);

Y para deshabilitar el enfoque en todos los elementos de la lista:

myListView.setFocusable(false);
myListView.setFocusableInTouchMode(false);
myListView.setClickable(false);

Funciona bien para mí, puedo hacer clic en los botones con pantalla táctil y también permite enfocar un clic usando el teclado

Fabricio PH
fuente
2
¡Solo este me ayudó! ¡Muchas gracias!
Onur
¿Qué pasa con un ExpandableListView? Tengo varias vistas en el elemento, solo quiero que se pueda hacer clic en las vistas del niño, gracias.
twlkyao
12

No tengo mucha experiencia que los usuarios anteriores, pero me enfrenté a este mismo problema y lo resolví con la siguiente solución

<Button
        android:id="@+id/btnRemove"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        android:layout_toRightOf="@+id/btnEdit"
        android:layout_weight="1"
        android:background="@drawable/btn"
        android:text="@string/remove" 
        android:onClick="btnRemoveClick"
        />

Evento btnRemoveClick Click

public void btnRemoveClick(View v)
{
    final int position = listviewItem.getPositionForView((View) v.getParent()); 
    listItem.remove(position);
    ItemAdapter.notifyDataSetChanged();

}
Bhavin Chauhan
fuente
solución interesante
sherpya 01 de
9

Probablemente hayas encontrado cómo hacerlo, pero puedes llamar

ListView.setItemsCanFocus(true)

y ahora tus botones se enfocarán

Rafal
fuente
5

No estoy seguro de ser la mejor manera, pero funciona bien y todo el código permanece en su ArrayAdapter.

package br.com.fontolan.pessoas.arrayadapter;

import java.util.List;

import android.content.Context;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ImageView;
import br.com.fontolan.pessoas.R;
import br.com.fontolan.pessoas.model.Telefone;

public class TelefoneArrayAdapter extends ArrayAdapter<Telefone> {

private TelefoneArrayAdapter telefoneArrayAdapter = null;
private Context context;
private EditText tipoEditText = null;
private EditText telefoneEditText = null;
private ImageView deleteImageView = null;

public TelefoneArrayAdapter(Context context, List<Telefone> values) {
    super(context, R.layout.telefone_form, values);
    this.telefoneArrayAdapter = this;
    this.context = context;
}

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    View view = inflater.inflate(R.layout.telefone_form, parent, false);

    tipoEditText = (EditText) view.findViewById(R.id.telefone_form_tipo);
    telefoneEditText = (EditText) view.findViewById(R.id.telefone_form_telefone);
    deleteImageView = (ImageView) view.findViewById(R.id.telefone_form_delete_image);

    final int i = position;
    final Telefone telefone = this.getItem(position);
    tipoEditText.setText(telefone.getTipo());
    telefoneEditText.setText(telefone.getTelefone());

    TextWatcher tipoTextWatcher = new TextWatcher() {
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void afterTextChanged(Editable s) {
            telefoneArrayAdapter.getItem(i).setTipo(s.toString());
            telefoneArrayAdapter.getItem(i).setIsDirty(true);
        }
    };

    TextWatcher telefoneTextWatcher = new TextWatcher() {
        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void afterTextChanged(Editable s) {
            telefoneArrayAdapter.getItem(i).setTelefone(s.toString());
            telefoneArrayAdapter.getItem(i).setIsDirty(true);
        }
    };

    tipoEditText.addTextChangedListener(tipoTextWatcher);
    telefoneEditText.addTextChangedListener(telefoneTextWatcher);

    deleteImageView.setOnClickListener(new OnClickListener() {
        @Override
        public void onClick(View v) {
            telefoneArrayAdapter.remove(telefone);
        }
    });

    return view;
}

}
mFontolan
fuente
3

Sé que es tarde, pero esto puede ayudar, este es un ejemplo de cómo escribo una clase de adaptador personalizado para diferentes acciones de clic

 public class CustomAdapter extends BaseAdapter {

    TextView title;
  Button button1,button2;

    public long getItemId(int position) {
        return position;
    }

    public int getCount() {
        return mAlBasicItemsnav.size();  // size of your list array
    }

    public Object getItem(int position) {
        return position;
    }

    public View getView(int position, View convertView, ViewGroup parent) {

        if (convertView == null) {
            convertView = getLayoutInflater().inflate(R.layout.listnavsub_layout, null, false); // use sublayout which you want to inflate in your each list item
        }

        title = (TextView) convertView.findViewById(R.id.textViewnav); // see you have to find id by using convertView.findViewById 
        title.setText(mAlBasicItemsnav.get(position));
      button1=(Button) convertView.findViewById(R.id.button1);
      button1.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            //your click action 

           // if you have different click action at different positions then
            if(position==0)
              {
                       //click action of 1st list item on button click
        }
           if(position==1)
              {
                       //click action of 2st list item on button click
        }
    });

 // similarly for button 2

   button2=(Button) convertView.findViewById(R.id.button2);
      button2.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            //your click action 

    });



        return convertView;
    }
}
Manohar Reddy
fuente
-4

¿No es la solución de plataforma para esta implementación utilizar un menú contextual que se muestra en una pulsación larga?

¿El autor de la pregunta conoce los menús contextuales? Apilar botones en una vista de lista tiene implicaciones de rendimiento, desordenará su interfaz de usuario y violará el diseño de interfaz de usuario recomendado para la plataforma.

Por otro lado; Los menús contextuales, por naturaleza de no tener una representación pasiva, no son obvios para el usuario final. Considere documentar el comportamiento?

Esta guía debería darte un buen comienzo.

http://www.mikeplate.com/2010/01/21/show-a-context-menu-for-long-clicks-in-an-android-listview/

Gusdor
fuente