Подтвердить что ты не робот

Android, List Adapter возвращает неправильную позицию в getView

Я нашел загадочную проблему, которая может быть ошибкой! У меня есть список в моем фрагменте. Каждая строка имеет кнопку. Список не должен отвечать на клик, однако кнопки доступны.

Чтобы получить нажатую кнопку, я создал слушателя и реализую его в своем фрагменте. Это код моего адаптера.

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;
    }
}

Когда я запускаю приложение, я могу видеть мои строки, так как мой список длинный и имеет более 200 элементов, когда я перебираюсь в середине списка и нажимаю на элемент, а возвращаемая позиция неверна (это что-то вроде 7, иногда 4 и т.д..).

Теперь, что такое тайна? Если я активен при прослушивании списка элементов из моего фрагмента и нажимаю на строку, тогда правильная позиция строки будет отображаться во время этой строки, если я нажму кнопку, тогда будет отображаться неправильная позиция.

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

Результат в logcat:

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

Любое предложение будет оценено по достоинству. Благодаря

4b9b3361

Ответ 1

Поскольку convertView и держатель будут переработаны для использования, переместите setOnClickListener из инструкции 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));
            }
        });

Это не лучшее решение для этого, потому что будет некоторая проблема с производительностью. Я предлагаю вам создать карту для вашего представления и создать новое представление для вашего элемента, а затем просто использовать относительный вид для каждого вида.

Я думаю, что это будет лучшее решение с лучшей производительностью:

@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;
}

Вы также можете самостоятельно управлять своим представлением. Создайте каждый уникальный вид для своего элемента, не перерабатывайте представление.

//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;
}

Ответ 2

Вы пытались сделать что-то вроде этого:

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

И затем извлеките, какая строка была нажата в обратном вызове для кнопки, например:

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

Ответ 3

Другим подходом, который я счел полезным (если вы используете шаблон ViewHolder, конечно), является установка индекса по отдельному атрибуту всякий раз, когда вызывается getView(), а затем внутри вашего onClickListener вам просто нужно ссылаться на атрибут позиции держателя, что-то например:

@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;

}