Как избежать обновления ячеек при вызове notifyDataSetChanged () для PinterestLikeAdapterView?

Фон

Я использую библиотеку PinterestLikeAdapterView для отображения некоторых изображений из Интернета, что похоже на gridView, но с разной высотой для каждой ячейки.

Эта проблема

Поскольку я использую эту библиотеку для отображения изображений из Интернета, очень важно, чтобы при вызове notifyDatasetChanged не происходило беспорядка в представлениях.

По какой-то причине вызов этой функции вызвал бы метод getView () с разными позициями для представлений. например, хотя я вообще не прокручиваю и вызываю notifyDatasetChanged (или addAll в случае, если это ArrayAdapter), для позиции 0 он будет иметь вид позиции 8, для позиции 1 он будет принимать вид позиции 7 и так далее ...

Это заставляет всю сетку обновлять изображения и тем самым разрушает UX.

Обычно как в gridView, так и в listView способ преодоления обновления состоит в том, чтобы поместить позицию, которая использовалась для представления, внутри viewHolder, и если они равны, это означает, что они все еще совпадают.

Например:

... getView(...)
  {
  //<=inflate a new view if needed 
  //avoid refreshing view in case it's still the same position:
  if(position==holder.position)
    return rootView;
  holder.position=position;
  //<=update the view according to its data
  ...
  }

Однако здесь они повторно используют другие представления в другом порядке, поэтому этот трюк здесь не сработает.

Из-за этой проблемы я не только получаю обновления почти всех видимых представлений, но, поскольку я использую библиотеку DiskCacheLru, она вылетает, поскольку он пытается поместить 2 идентичных данных inputSteam в один и тот же ключ, используя 2 потока.

Вопрос

Что я могу сделать? Это известная ошибка в библиотеке?

Может я плохой способ побороть обновления?

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


person android developer    schedule 08.10.2013    source источник
comment
Вы имеете в виду, что порядок просмотров в беспорядке?   -  person Zhenghong Wang    schedule 08.10.2013
comment
да и нет. сетка как-то хорошо их показывает. просто он вызывает адаптер в странном порядке. возможно, я не очень хорошо объяснил пример: хотя я вообще не прокручивал и вызываю notifyDataSetChanged (), метод getView вызовет позицию 0 и предоставит мне вид позиции 8 вместо реального вида, который был используется для позиции 0. Проблема в том, что из-за этого я не могу преодолеть обновление представлений, и поэтому похоже, что он перезагружает изображения. Я думаю, что кеш памяти может помочь, но на самом деле это не исправление, так как могут быть другие случаи, о которых я не думал.   -  person android developer    schedule 08.10.2013
comment
Я всегда думал, что после notifyDataSetChanged у вас не будет старых convertView в getView.   -  person Sherif elKhatib    schedule 14.10.2013
comment
@SherifelKhatib он тоже получит это здесь, но странным образом - для каждой позиции вы получаете неправильное представление, поэтому вы не можете использовать его более старое состояние, когда оно уже соответствует позиции. это означает, что в таком случае обновление ячейки неизбежно.   -  person android developer    schedule 14.10.2013
comment
Совершенно нормально получить другое представление, но я подумал, что они должны быть нулевыми. Попробуйте этот прием: 1 - переопределите getViewTypeCount() и return getCount(); внутри него. 2- переопределить getItemViewType(int position) и return position; внутри него.   -  person Sherif elKhatib    schedule 14.10.2013
comment
@SherifelKhatib, я не понимаю, как создание новых типов может помочь, поскольку это заставит создавать новые представления для кеширования. мне нужно избегать обновления представлений, когда они не нужны. Кроме того, я уже использую getViewTypeCount () и getItemViewType (), потому что он мне нужен для разных типов информации, но даже когда это один тип, я получаю ту же проблему.   -  person android developer    schedule 14.10.2013


Ответы (4)


Краткий ответ:

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

Длинный ответ:

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

Это делается из соображений производительности: накачать новый Views сложно и требуется время, поэтому AdapterView старается делать это как можно реже.

При использовании держателя вы сохраняете ссылки на ImageView и TextView дочерних элементов внутри View элемента, поэтому вам не нужно каждый раз искать их с помощью findViewById() - обычно вы не сохраняете ничего особенного для конкретной позиции, потому что View и его держатель будет часто использоваться для разных позиций.

Теперь, когда вы вызываете notifyDataSetChanged(), AdapterView предполагает, что набор данных полностью изменился. Изображение, которое было связано с позицией 8, может больше не присутствовать, или оно может быть теперь связано с позицией 12. Следовательно, все существующие Views отменяются, но поскольку AdapterView по-прежнему хотел бы избежать раздувания новых Views, они повторно используются для отображения новых данных, независимо от того, какую позицию они отображали ранее.

Это объясняет, почему getView() передается одно и то же View для разных позиций и почему видимые позиции обновляются при вызове notifyDataSetChanged(). Но как избежать обновления изображений, которые портят пользовательский опыт?

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

person cnnr    schedule 19.10.2013
comment
Я знаю, как работает адаптерView, и использовал позицию в держателе просмотра, чтобы избежать обновления представлений, которые уже синхронизированы с данными. проблема в том, что этот трюк не работает с этой библиотекой, так как он нарушает порядок просмотров, даже если прокрутка не выполнялась. насчет библиотеки, которую вы предложили, это кажется приятным, но я не уверен, может ли это помочь, так как мне нужно иметь файл как малого, так и большого размера (в соответствии с экраном), поэтому я загружаю файл, а затем делаю соответственно понижающая дискретизация. при нажатии на изображение отображается его полная версия. - person android developer; 19.10.2013
comment
Большое спасибо.. !! он работал нормально, когда я использовал Picasso вместо скольжения и универсального загрузчика изображений. - person user2323471; 14.01.2016

View getView(int position, View view, ViewGroup parent) всегда будет вызываться по возрастанию после notifyDataSetChanged().
Я предполагаю, что порядок завершения задачи загрузки вызовет эту проблему.
Как вы упомянули в своем вопросе, сохранение позиции - хороший способ избежать этой проблемы.

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

Сохраняйте слабую ссылку на каждую ImageView в задаче загрузки.
Затем оберните задачу загрузки в фиктивный ColorDrawable.
Когда вызывается getView, установите фиктивный ColorDrawable на ImageView и начните загрузку. Когда загрузка будет завершена, снова установите для загруженного образа значение, указанное в ImageView в OnPostExecute().

Пояснение к http://android-developers.blogspot.jp/2010/07/multithreading-for-performance.html
Исходный код
https://code.google.com/p/android-imagedownloader/source/checkout

person Zhenghong Wang    schedule 09.10.2013
comment
Нет, задачи тут ни при чем. задачи будут фактически отменены, поскольку позиция getView не совпадает с позицией viewHolder. здесь происходит что-то вроде прокрутки, хотя прокрутки не было. в качестве примечания, я хотел бы узнать немного больше об идее, которую вы представили, но она не может решить проблему, которую я показал. - person android developer; 09.10.2013

Есть очень хороший пример на PinterestLikeListView в GitHub.

Вот библиотека StaggeredGridView

Модифицированная версия экспериментального Android StaggeredGridView. Включает собственные OnItemClickListener и OnItemLongClickListener, селектор и восстановление фиксированной позиции.

Вы можете получить проект библиотеки здесь library

и вы можете получить демонстрационный проект здесь

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

введите описание изображения здесь

Надеюсь, эта библиотека поможет вам.

person Amit Gupta    schedule 20.10.2013
comment
к сожалению, у этой библиотеки еще больше проблем, таких как отображение пустых ячеек и NPE. Я уже писал сообщение о поиске лучшей библиотеки, которая показывает такую ​​сетку, здесь: stackoverflow.com/questions/18557720/. ни одна из найденных мной библиотек не содержит ошибок. у каждого есть свои странные проблемы. - person android developer; 21.10.2013

похоже, что авторы этой библиотеки исправили это, спустя какое-то время я сообщил об этом:

https://github.com/huewu/PinterestLikeAdapterView/issues/8

person android developer    schedule 11.12.2013