Android: éléments ListView avec plusieurs boutons cliquables

183

J'ai un ListViewoù chaque élément de la liste contient un TextView et deux boutons différents. Quelque chose comme ça:

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

Avec ce code, je peux créer un OnItemClickListenerpour l'élément entier:

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

        }

    }
});

Cependant, je ne veux pas que l'élément entier soit cliquable, mais uniquement les deux boutons de chaque élément de liste.

Ma question est donc de savoir comment implémenter un onClickListener pour ces deux boutons avec les paramètres suivants:

  • int button (sur quel bouton de l'élément a été cliqué)
  • int position (qui est l'élément de la liste sur lequel le clic sur le bouton s'est produit)

Mise à jour: j'ai trouvé une solution comme décrit dans ma réponse ci-dessous. Maintenant, je peux cliquer / appuyer sur le bouton via l'écran tactile. Cependant, je ne peux pas le sélectionner manuellement avec le trackball. Il sélectionne toujours l'élément de liste entier et à partir de là, il passe directement à l'élément de liste suivant en ignorant les boutons, même si j'ai défini .setFocusable(true)et setClickable(true)pour les boutons dans getView().

J'ai également ajouté ce code à mon adaptateur de liste personnalisé:

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

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

Cela fait qu'aucun élément de liste ne peut plus être sélectionné. Mais cela n'a pas aidé à rendre les boutons imbriqués sélectionnables.

Quelqu'un a une idée?

znq
la source
Sont-ils encore nécessaires?
Stuart Axon
Si vous regardez le code BaseAdapter, vous verrez que areAllItemsEnabled () et isEnabled () sont simplement codés en dur sur true, ce qui en fait de simples espaces réservés sans aucune logique.
Artem Russakovskii
Que faire si je souhaite utiliser un SimpleCursorAdapter? Dois-je créer un adaptateur personnalisé étend simplecursoradapter?
oratis

Réponses:

150

La solution à cela est en fait plus facile que je ne le pensais. Vous pouvez simplement ajouter dans la getView()méthode de votre adaptateur personnalisé un setOnClickListener () pour les boutons que vous utilisez.

Toutes les données associées au bouton doivent être ajoutées myButton.setTag()dans le getView()et sont accessibles dans onClickListener viaview.getTag()

J'ai posté une solution détaillée sur mon blog sous forme de tutoriel.

znq
la source
2
Fixé. Merci d'avoir signalé :-)
znq
avec quelle précision avez-vous obtenu le curItem.url de la requête, seriez-vous plus précis?
oratis
1
Merci znq, cela a été très utile pour moi ... Gain de temps.
Nandagopal T
Regardez cette conférence à 11:39 il y a un excellent exemple: youtu.be/wDBM6wVEO70?t=11m39s Ensuite, faites ce que @znq dit ... setTag () lorsque convertView == null et faites getTag () dans le onClick () méthode de onClickListener () du bouton. Merci!
Shehaaz
13
ce serait formidable d'avoir des réponses dans SO lui-même et de ne pas pointer vers une autre page. Le lien du blog est mort!
gsb
63

C'est en quelque sorte la réponse d'un appendice @ znq ...

Il existe de nombreux cas où vous souhaitez connaître la position de la ligne pour un élément cliqué ET vous souhaitez savoir quelle vue de la ligne a été sélectionnée. Cela sera beaucoup plus important dans les interfaces utilisateur des tablettes.

Vous pouvez le faire avec l'adaptateur personnalisé suivant:

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
la source
6
ou vous pouvez également utiliser quelque chose comme ceci ListView mListView = (ListView) v.getParent (). getParent ();
max4ever
1
Comment puis-je obtenir la position si j'utilise l'adaptateur dans une classe distincte. J'ai simplement utilisé la position dans OnClickListener, quelque chose comme ça comme_c.get (position). Il suggère de rendre la position finale dans la méthode getView de l'adaptateur. Si je fais ce changement, la position que j'obtiens est la même pour tous les éléments de la lisview. Pouvez-vous me suggérer comment résoudre ce problème.
Manikandan
C'est parce que lorsque vous utilisez le paramètre de position de la méthode getView, il vous donne la position de l'élément de la liste qui est le plus récemment créé mais pas celui sur
lequel
@Manikandan: Si vous utilisez la méthode getView () de l'adaptateur, vous avez déjà la position qui vous est passée dans les paramètres de la méthode. Dans ce cas, vous pouvez utiliser quelque chose comme setTag () pour stocker les informations de position.
greg7gkb
1
J'utilise votre code dans mon application mais l'événement de clic est appelé après un délai ou lorsque j'essaie de faire défiler la liste. Des idées pourquoi cela se produit?
Abdullah Umer
22

Pour les futurs lecteurs:

Pour sélectionner manuellement les boutons avec le trackball, utilisez:

myListView.setItemsCanFocus(true);

Et pour désactiver le focus sur l'ensemble des éléments de la liste:

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

Cela fonctionne bien pour moi, je peux cliquer sur les boutons avec écran tactile et permet également de concentrer un clic à l'aide du clavier

Rudolf Real
la source
2
Seul celui-ci m'a aidé! Merci beaucoup!
Onur
Qu'en est-il d'un ExpandableListView, j'ai plusieurs vues dans l'élément, je veux juste que les vues de l'enfant cliquables, merci.
twlkyao
12

Je n'ai pas beaucoup d'expérience que les utilisateurs ci-dessus, mais j'ai rencontré le même problème et j'ai résolu cela avec la solution ci-dessous

<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"
        />

btnRemoveClick Click événement

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

}
Bhavin Chauhan
la source
solution intéressante
sherpya
9

Vous avez probablement trouvé comment le faire, mais vous pouvez appeler

ListView.setItemsCanFocus(true)

et maintenant vos boutons attireront l'attention

Rafal
la source
5

Je ne suis pas sûr d'être le meilleur moyen, mais fonctionne correctement et tout le code reste dans votre 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
la source
3

Je sais qu'il est tard mais cela peut aider, voici un exemple comment j'écris une classe d'adaptateur personnalisée pour différentes actions 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
la source
-4

La solution de plate-forme pour cette implémentation n'est-elle pas d'utiliser un menu contextuel qui s'affiche sur un appui long?

L'auteur de la question connaît-il les menus contextuels? L'empilement de boutons dans une liste a des implications sur les performances, encombrera votre interface utilisateur et enfreindra la conception d'interface utilisateur recommandée pour la plate-forme.

Sur le revers; les menus contextuels - par nature de ne pas avoir de représentation passive - ne sont pas évidents pour l'utilisateur final. Pensez à documenter le comportement?

Ce guide devrait vous donner un bon départ.

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

Gusdor
la source