getView вызывается с неправильной позицией при быстрой прокрутке - PullRequest
9 голосов
/ 15 марта 2012

довольно новый разработчик Android здесь.

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

У меня есть собственный набор listViewиспользуя отдельный класс, который расширяет BaseAdapter и использует держатель представления (я настроил это, рассматривая различные примеры пользовательских списков).У него есть текстовое представление, кнопка, индикатор выполнения и кнопка с изображением, и эти элементы скрыты / изменены в соответствии с AsyncTask, использованным для загрузки файла.

Все отлично работает, когда я прокручиваю списокмедленно.Когда я быстро прокручиваю список вверх / вниз, создается впечатление, что представления некоторых ячеек в списке переназначаются другим ячейкам.

Я помещаю эту строку в верхнюю часть моего метода getView вМой класс адаптера:

    @Override
    public View getView(final int pos, View convertView, ViewGroup parent)
    {
        Log.i("ks3","getView called, position is " + pos);

У меня есть 7 элементов в списке, и 6 из них помещаются на экране.Когда он отображается впервые, я вижу это в своем журнале:

getView called, position is 0
getView called, position is 1
getView called, position is 2
getView called, position is 3
getView called, position is 4
getView called, position is 5

Когда я медленно прокручиваю вниз до следующего элемента, это распечатывается следующим образом:

getView called, position is 6

Когда я прокручиваювернемся к следующему:

getView called, position is 0

Медленное перемещение вниз и вверх приводит к безупречным результатам.

Когда я начинаю быстро прокручивать назад и вперед, он выводит это сообщение несколько раз, в основномпоказывая 6 и 0 в качестве позиции, но иногда я также вижу их:

getView called, position is 1
getView called, position is 5

Не следует вызывать позиции 1 и 5, так как они всегда на экране.Это как если бы getView запутался.В то же время, как я уже говорил, мой список будет выглядеть странно.Кнопки с изображением будут перемещаться вверх или вниз по ячейке, где их не должно быть.

Если я вернусь к плавной прокрутке, я снова увижу только 0 и 6 для позиции.

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

Спасибо!

Редактировать: я хотел обновитьпара вещей, касающихся этого вопроса.Во-первых, из комментария здесь и из видео Google о listView мое внимание привлекло то, что getView можно вызывать не только для того, что я себе представлял, например, для измерений, поэтому меня не должно пугатьто, что я изначально считал частью моей проблемы (то, что я думал, что getView вызывается с неправильными позициями).

Во-вторых, я неоднократно видел, что это очень плохая идея - "кэшировать представления внутриваш адаптер. "Я не совсем понимаю, что это значит, но я почти уверен, что это одна из вещей, которые я делаю неправильно (я сохраняю экземпляр кнопки, progressBar, imageButton ...).

Этонаряду с тем, что я обновляю представления вне getView и не использую notifyDataSetChanged ();они вместе, вероятно, вызывают некоторые неприятные вещи.

Ответы [ 5 ]

13 голосов
/ 15 марта 2012

Вы правы, что ListView повторно использует представления в разных местах на экране. Это оптимизация, позволяющая разумно и быстро использовать память, не выделяя постоянно новые представления.

Скорее всего, вы используете LiewView неправильно. Смотрите этот разговор о том, как правильно использовать ListView, чтобы получить всю историю, но вот основные моменты:

  1. «Положение» относится к расположению ваших данных в списке адаптеров. Если данные вашего адаптера находятся в массиве, это будет индекс в этом массиве.
  2. «Идентификатор» относится к значению самих данных. Если у вас есть список имен и к ним прибегают, их позиция изменится, но их ID не изменится.
  3. «Индекс» относится к относительному местоположению вида относительно видимой области экрана. Возможно, вам это никогда не понадобится.
  4. Не манипулируйте представлениями (и не пытайтесь их кэшировать) вне метода getView() вашего адаптера, иначе вы получите странное поведение.
  5. Если вызов getView(int, View, ViewGroup) предоставляет экземпляр представления, заполните его поля вместо того, чтобы надувать совершенно новые представления. Если вы правильно внедрили getItemType(), вы всегда получите правильный тип View для повторного заполнения.
  6. Сделайте ваш метод getView() настолько быстрым, насколько это возможно, и выполняйте тяжелую работу только с другими нитями.
  7. Тот факт, что контейнер с именем getView() не обязательно означает, что данные будут отображаться. Каркас использует их для целей измерения. Поскольку работа может быть выброшена, это еще одна причина убедиться, что getView() так быстро, как вы можете.
  8. Когда что-то происходит с вашими данными, которые вам нужно показать на экране, скажем, загрузка завершена, вот когда вы звоните notifyDataSetChanged(). Не вмешивайтесь в представления напрямую, они будут заполнены в следующем цикле пользовательского интерфейса, когда он будет перерисован.

Потратив всего несколько дней на переработку ListView, который был реализован наивно, я чувствую вашу боль. Хотя результаты того стоили!

4 голосов
/ 22 сентября 2012

всегда используйте этот подход в getview: convertView = inflater.inflate (R.layout.listinflate, parent, false);

И пусть ваша деятельность реализует onScrollListener и, когда пользователь перебрасывает, уведомляет ваш адаптер списка, чтобы не забыть предпринять усилия для корректного обновления элементов. и когда пользователь перестанет бросать вызов, попросите адаптер позаботиться о предоставлении данных в getView.

Это решило все мои проблемы. И еще одна вещь, не используйте wrap_content в высоте представления списка. установите для него значение smoothscroll false.

1 голос
/ 20 марта 2014

У меня была та же проблема: в адаптере я менял цвет фона в методе getView, но иногда представление списка было «рецидивирующим» представлениями (получая неправильный фон).Я решил просто поставить "else" каждому

if(...){changeBackground}

Я добавил

else {restore default background}

И тогда все заработало

0 голосов
/ 09 июня 2017

Для записи и расширения ответа @ Марк

Android выполняет следующую задачу. Допустим, у меня есть список из 100 элементов. Когда вы загружаете, getview вызывается для элемента от 0,1,2,3 ... до 10, например. И если мы продолжим, следующей позицией будет 11. Однако позиция 11 повторяет тот же вид, что и позиция 0. Таким образом, вид для позиции 11 существует, но имеет неправильное значение (значение 0).

Ответ: Изменить данные, если представление является нулевым или нет. Если представление является нулевым, тогда выкачайте и измените значения представления. Если нет, измените значения представления.

Допустим, у меня есть item_view (макет) с одним textview, тогда адаптер должен быть следующим:

Правильная версия:

public class XXXAdapter extends ArrayAdapter<XXX> {....
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
   if (convertView==null) {
       LayoutInflater inflater = LayoutInflater.from(this.getContext());
       convertView = inflater.inflate(R.layout.**itemofthelayout**, parent, false);
   }
   ***Object** item=this.getItem(position);
   TextView txt= (TextView) convertView.findViewById(R.id.**idsometextviewinsidelayout**);
   txt.setText(String.valueOf(position));
   return convertView;
   // return super.getView(position, convertView, parent);
}

Неправильная версия:

public class XXXAdapter extends ArrayAdapter<XXX> {....
@NonNull
@Override
public View getView(int position, @Nullable View convertView, @NonNull ViewGroup parent) {
   if (convertView==null) {
       LayoutInflater inflater = LayoutInflater.from(this.getContext());
       convertView = inflater.inflate(R.layout.**itemofthelayout**, parent, false);
       ***Object** item=this.getItem(position);
       TextView txt= (TextView) convertView.findViewById(R.id.**idsometextviewinsidelayout**);
       txt.setText(String.valueOf(position));
       return convertView;
       // return super.getView(position, convertView, parent);
   }
}
0 голосов
/ 15 марта 2012

getView вызывается для каждого элемента списка, который должен быть нарисован, но ранее не отображался на экране. Если вы прокручиваете быстро, вы можете получить странные позиции, но это не должно вызывать ошибок, как вы описываете (я думаю, что в Android нет ошибок, поскольку эти ListViews довольно хорошо протестированы). Например. когда вы прокручиваете достаточно быстро, у вас может быть 2 вида, которые нужно нарисовать. Возможно, в вашем коде что-то не так.

...