BaseAdapter заставляет ListView выходить из строя при прокрутке

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

Очень сложно описать точное поведение, но легко увидеть, если вы возьмете отсортированный список из 50 элементов и начнете прокрутку вверх и вниз.

class ContactAdapter extends BaseAdapter {

    ArrayList<Contact> mContacts;

    public ContactAdapter(ArrayList<Contact> contacts) {
        mContacts = contacts;
    }

    @Override
    public int getCount() {
        return mContacts.size();
    }

    @Override
    public Object getItem(int position) {
        return mContacts.get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view;
        if(convertView == null){
            LayoutInflater li = getLayoutInflater();
            view = li.inflate(R.layout.groups_item, null);
            TextView label = (TextView)view.findViewById(R.id.groups_item_title);
            label.setText(mContacts.get(position).getName());
            label = (TextView)view.findViewById(R.id.groups_item_subtitle);
            label.setText(mContacts.get(position).getNumber());
        }
        else
        {
            view = convertView;
        }
        return view;
    }

}

Ответ 1

Вы только помещаете данные в виджеты TextView, когда они сначала создаются. Вам нужно переместить эти четыре строки:

        TextView label = (TextView)view.findViewById(R.id.groups_item_title);
        label.setText(mContacts.get(position).getName());
        label = (TextView)view.findViewById(R.id.groups_item_subtitle);
        label.setText(mContacts.get(position).getNumber());

после блока if/else и перед возвратом метода, поэтому вы обновляете виджеты TextView, если вы перерабатываете строку или создаете новую.

Ответ 2

Чтобы подробнее разъяснить ответ CommonsWare, вот еще информация:

Операция li.inflate(необходимая для разбора компоновки строки из XML и создания соответствующего объекта View) завершается оператором if (convertView == null) для эффективности, поэтому инфляция одного и того же объекта не будет происходить снова и снова каждый раз, когда он появляется.

ОДНАКО, другие части метода getView используются для установки других параметров и поэтому НЕ должны быть включены в оператор if (convertView == null) {}... else {}.

Во многих распространенных реализациях этого метода некоторые элементы textView, ImageView или ImageButton должны заполняться значениями из списка [позиция], используя findViewById и после этого .setText или .setImageBitmap. Эти операции должны выполняться после создания представления с нуля путем инфляции и получения существующего представления, если оно не является нулевым (например, при обновлении).

Еще один хороший пример, когда это решение применяется для ListView ArrayAdapter, отображается в fooobar.com/info/22031/...