web-dev-qa-db-fra.com

Android, l'adaptateur de liste renvoie une mauvaise position dans getView

J'ai trouvé un mystérieux problème qui pourrait être un bug! J'ai une liste dans mon fragment. Chaque ligne a un bouton. La liste ne doit pas répondre au clic, mais les boutons sont cliquables.

Afin d'obtenir le bouton sur lequel j'ai cliqué, j'ai créé un écouteur et je l'ai implémenté dans mon fragment. Ceci est le code de mon adaptateur.

public class AddFriendsAdapter extends BaseAdapter {

    public interface OnAddFriendsListener {
        public void OnAddUserClicked(MutualFriends user);
    }

    private final String TAG = "*** AddFriendsAdapter ***";

    private Context context;
    private OnAddFriendsListener listener;
    private LayoutInflater myInflater;
    private ImageDownloader imageDownloader;
    private List<MutualFriends> userList;

    public AddFriendsAdapter(Context context) {
        this.context = context;
        myInflater = LayoutInflater.from(context);

        imageDownloader = ImageDownloader.getInstance(context);
    }

    public void setData(List<MutualFriends> userList) {
        this.userList = userList;

        Log.i(TAG, "List passed to the adapter.");
    }

    @Override
    public int getCount() {
        try {
            return userList.size();
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }

    @Override
    public Object getItem(int position) {
        return null;
    }

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

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        ViewHolder holder;

        if (convertView == null) {
            convertView = myInflater.inflate(R.layout.list_add_friends_row, null);
            holder = new ViewHolder();

            Typeface font = Typeface.createFromAsset(context.getAssets(), "fonts/ITCAvantGardeStd-Demi.ttf");
            holder.tvUserName = (TextView) convertView.findViewById(R.id.tvUserName);
            holder.tvUserName.setTypeface(font);
            holder.ivPicture = (ImageView) convertView.findViewById(R.id.ivPicture);
            holder.btnAdd = (Button) convertView.findViewById(R.id.btnAdd);
            holder.btnAdd.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Log.e(TAG, "Item: " + position);
                    listener.OnAddUserClicked(userList.get(position));
                }
            });

            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        holder.tvUserName.setText(userList.get(position).getName());
        imageDownloader.displayImage(holder.ivPicture, userList.get(position).getPhotoUrl());

        return convertView;
    }

    public void setOnAddClickedListener(OnAddFriendsListener listener) {
        this.listener = listener;
    }

    static class ViewHolder {
        TextView tvUserName;
        ImageView ivPicture;
        Button btnAdd;
    }
}

Lorsque je lance l'application, je peux voir mes lignes, car ma liste est longue et contient plus de 200 éléments lorsque je suis au milieu de la liste et cliquez sur un élément, puis que la position renvoyée est incorrecte (c'est quelque chose comme 7, parfois 4 et etc.).

Maintenant quel est le mystère? Si j'active l'écouteur d'élément de la liste de mon fragment et que je clique sur la ligne, la position correcte de la ligne sera affichée tandis que sur cette ligne si je clique sur le bouton, la mauvaise position sera affichée.

listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
                Log.e(TAG, "item " + position + " clicked.");
            }
        });

Résultat dans logcat:

05-09 10:22:25.228: E/AddFriendsFragment(20296): item 109 clicked.
05-09 10:22:34.453: E/*** AddFriendsAdapter ***(20296): Item: 0

Toute suggestion serait appréciée. Merci

23
Hesam

Comme le convertView et le support seront recyclés pour être utilisés, déplacez votre setOnClickListener hors de l'instruction if else:

    if (convertView == null) {
        convertView = myInflater.inflate(R.layout.list_add_friends_row, null);
        holder = new ViewHolder();

        Typeface font = Typeface.createFromAsset(context.getAssets(), "fonts/ITCAvantGardeStd-Demi.ttf");
        holder.tvUserName = (TextView) convertView.findViewById(R.id.tvUserName);
        holder.tvUserName.setTypeface(font);
        holder.ivPicture = (ImageView) convertView.findViewById(R.id.ivPicture);
        holder.btnAdd = (Button) convertView.findViewById(R.id.btnAdd);
        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }
    holder.btnAdd.setOnClickListener(new View.OnClickListener() {
         @Override
         public void onClick(View v) 
                Log.e(TAG, "Item: " + position);
                listener.OnAddUserClicked(userList.get(position));
            }
        });

Ce n'est pas la meilleure solution pour cela, car il y aura un problème de performances. Je vous suggère de créer une carte pour votre vue et de créer une nouvelle vue pour votre article, puis d'utiliser simplement la vue relative pour chaque vue.

Je pense que ce sera une meilleure solution avec les meilleures performances:

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    ViewHolder holder;

    if (convertView == null) {
        convertView = myInflater.inflate(R.layout.list_add_friends_row, null);
        holder = new ViewHolder();

        Typeface font = Typeface.createFromAsset(context.getAssets(), "fonts/ITCAvantGardeStd-Demi.ttf");
        holder.tvUserName = (TextView) convertView.findViewById(R.id.tvUserName);
        holder.tvUserName.setTypeface(font);
        holder.ivPicture = (ImageView) convertView.findViewById(R.id.ivPicture);
        holder.btnAdd = (Button) convertView.findViewById(R.id.btnAdd);
        holder.btnAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Integer pos = (Integer)v.getTag();
                Log.e(TAG, "Item: " + pos);
                listener.OnAddUserClicked(userList.get(pos));
            }
        });

        convertView.setTag(holder);
    } else {
        holder = (ViewHolder) convertView.getTag();
    }

    holder.tvUserName.setText(userList.get(position).getName());
    imageDownloader.displayImage(holder.ivPicture, userList.get(position).getPhotoUrl());
    holder.btnAdd.setTag(position);
    return convertView;
}

Vous pouvez également gérer vous-même votre vue. Créez chaque vue unique pour votre article, ne recyclez pas la vue.

//member various
private Map<Integer, View> myViews = new HashMap<Integer, View>(); 

@Override
public View getView(final int position, View convertView, ViewGroup parent) {
    ViewHolder holder;
    View view = myViews.get(position);
    if (view == null) {
        view = myInflater.inflate(R.layout.list_add_friends_row, null);
        //don't need use the holder anymore.

        Typeface font = Typeface.createFromAsset(context.getAssets(), "fonts/ITCAvantGardeStd-Demi.ttf");
        holder.tvUserName = (TextView) convertView.findViewById(R.id.tvUserName);
        holder.tvUserName.setTypeface(font);
        holder.ivPicture = (ImageView) convertView.findViewById(R.id.ivPicture);
        holder.btnAdd = (Button) convertView.findViewById(R.id.btnAdd);
        holder.btnAdd.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Integer pos = (Integer)v.getTag();
                Log.e(TAG, "Item: " + pos);
                listener.OnAddUserClicked(userList.get(pos));
            }
        });

       holder.tvUserName.setText(userList.get(position).getName());
       imageDownloader.displayImage(holder.ivPicture,  
                userList.get(position).getPhotoUrl());
       myViews.put(position, view);

    }
    return view;
}
50
buptcoder

Une autre approche que j'ai trouvée utile (si vous utilisez le modèle ViewHolder bien sûr) consiste à définir l'index sur un attribut distinct chaque fois que getView () est appelé, puis dans votre onClickListener, vous n'avez qu'à référencer l'attribut de position de votre titulaire, quelque chose comme ceci:

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

    final ViewHolder holder;

    if(convertView == null){

        convertView = View.inflate(mContext, R.layout.contact_picker_row,null);

        holder = new ViewHolder();

        holder.body = (RelativeLayout)convertView.findViewById(R.id.numberBody);

        convertView.setTag(holder);

    }else{

        holder = (ViewHolder)convertView.getTag();

    }

    holder.position = position;

    holder.body.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {

            Toast.makeText(mContext,"Clicked on: "+holder.position,Toast.LENGTH_LONG).show();

        }
    });

    return convertView;
}

private class ViewHolder{

    RelativeLayout body;
    int position;

}
2
Alan Poggetti

Avez-vous essayé de faire quelque chose comme ça:

holder.btnAdd.setTag(Integer.valueOf(position));

Et puis récupérez la ligne sur laquelle vous avez cliqué dans le rappel du bouton, comme ceci:

public void btnAddClickListener(View view)
    {
        position = (Integer)view.getTag();
        Foo foo = (Foo)foos_adapter.getItem(position);  //get data of row(position)
        //do some
    }
2