Android: видимые изображения ListView мерцают при добавлении данных в ArrayAdapter
У меня есть специальный адаптер, который расширяет ArrayAdapter, он реализует шаблоны держателей для отображения данных (текст + изображение) из веб-службы.
для отложенной загрузки изображений я использую шаблон асинхронных задач из расширенного тренинга на сайте разработчиков Android,
Я также использую кэш диска RAM + RAM.
когда есть дополнительные данные для извлечения, я добавляю нижний колонтитул, щелкая по нему, чтобы получить дополнительные данные из веб-службы и добавить их в адаптер.
проблема заключается в том, что при добавлении этих новых данных некоторые видимые изображения изменяются и немедленно возвращаются назад, что приводит к странному мерцанию.
кроме этого все работает нормально, а прокрутка плавная.
насколько я понимаю, эти изменения изображения происходят при обновлении видимых видов при добавлении новых данных.
Есть ли способ обойти это нежелательное поведение?
это класс, выполняющий загрузку и управление асинхронными задачами
public class ImageDownloader {
private ImageCache mCache;
private int reqWidth;
private int reqHeight;
public void download(String url, ImageView imageView, ImageCache imageCache, int reqHeight, int reqWidth) {
mCache = imageCache;
this.reqHeight = reqHeight;
this.reqWidth = reqWidth;
if (cancelPotentialDownload(url, imageView)) {
BitmapDownloaderTask task = new BitmapDownloaderTask(imageView);
DownloadedDrawable downloadedDrawable = new DownloadedDrawable(task);
imageView.setImageDrawable(downloadedDrawable);
task.execute(url);
}
}
private class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {
private String url;
private WeakReference<ImageView> imageViewReference;
public BitmapDownloaderTask(ImageView imageView) {
imageViewReference = new WeakReference<ImageView>(imageView);
}
@Override
protected Bitmap doInBackground(String... strings) {
Bitmap bitmap = null;
try {
bitmap = mCache.getBitmapFromURL(strings[0], reqWidth, reqHeight);
} catch (IOException e) {
}
return bitmap;
}
@Override
protected void onPostExecute(Bitmap bitmap) {
if (isCancelled()) bitmap = null;
if (imageViewReference != null) {
ImageView imageView = imageViewReference.get();
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
private static class DownloadedDrawable extends ColorDrawable {
private WeakReference<BitmapDownloaderTask> bitmapDownloaderTaskReference;
public DownloadedDrawable(BitmapDownloaderTask bitmapDownloaderTask) {
bitmapDownloaderTaskReference = new WeakReference<BitmapDownloaderTask>(bitmapDownloaderTask);
}
public BitmapDownloaderTask getBitmapDownloaderTask() {
return bitmapDownloaderTaskReference.get();
}
}
private static BitmapDownloaderTask getBitmapDownloaderTask(ImageView imageView) {
if (imageView != null) {
Drawable drawable = imageView.getDrawable();
if (drawable instanceof DownloadedDrawable) {
DownloadedDrawable downloadedDrawable = (DownloadedDrawable)drawable;
return downloadedDrawable.getBitmapDownloaderTask();
}
}
return null;
}
private static boolean cancelPotentialDownload(String url, ImageView imageView) {
BitmapDownloaderTask bitmapDownloaderTask = getBitmapDownloaderTask(imageView);
if (bitmapDownloaderTask != null) {
String bitmapUrl = bitmapDownloaderTask.url;
if ((bitmapUrl == null) || (!bitmapUrl.equals(url))) {
bitmapDownloaderTask.cancel(true);
} else {
return false;
}
}
return true;
}
}
это адаптер:
закрытый класс PlaceAdapter extends ArrayAdapter {final int viewResourceId;
public PlaceAdapter(Context context, int textViewResourceId, List<PlaceModel> objects) {
super(context, textViewResourceId, objects);
viewResourceId = textViewResourceId;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if (convertView == null) {
LayoutInflater inflater = getLayoutInflater();
convertView = inflater.inflate(viewResourceId, null);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
PlaceModel place = getItem(position);
holder.name.setText(place.getName());
holder.address.setText(place.getVicinity());
holder.position = position;
if (place.getIcon() != null) {
String url = mImageViewUrls.get(holder.image);
if (url == null || (url != null && !url.equals(place.getIcon()))) {
mDownloader.download(place.getIcon(), holder.image, mCache, 100, 100);
mImageViewUrls.put(holder.image, place.getIcon());
}
} else {
holder.image.setImageBitmap(null);
mImageViewUrls.remove(holder.image);
}
return convertView;
}
}
private class ViewHolder {
public final ImageView image;
public final TextView name;
public final TextView address;
public int position;
public ViewHolder(View row) {
image = (ImageView) row.findViewById(R.id.placeRow_imageView);
name = (TextView) row.findViewById(R.id.placeRow_placeName);
address = (TextView) row.findViewById(R.id.placeRow_placeAddress);
}
}
mImageViewUrls
это WeakHashMap<ImageView, String>
что карты между ImageView
и URL, поэтому избыточные вызовы асинхронных задач можно уменьшить, проверив, ImageView
уже показывает требуемое изображение. без этой реализации мерцание происходит во всех видимых изображениях при изменении данных. при этом это происходит только с некоторыми изображениями.
РЕДАКТИРОВАТЬ: я пытался устранить возможные причины этой проблемы, сначала я попытался полностью обойти реализацию кэша и загрузить каждое растровое изображение из сети, затем я попытался обернуть мой адаптер с бесконечным адаптером CommonsWare и с обоими я получил тот же результат.. так это оставляет только класс ImageDownloader и мой адаптер как возможные причины.. Я полностью потерян на этом.
4 ответа
Причиной этого мерцания является то, что элементы списка просмотра используются повторно. При повторном использовании просмотры изображений в элементе списка сохраняют старую ссылку на изображение, которая отображается первой. Позже, как только новое изображение загружено, оно начинает показывать. это вызывает мерцающее поведение. Чтобы избежать этой мерцающей проблемы, всегда удаляйте старую ссылку на изображение из окна просмотра изображений, когда оно снова используется.
В вашем случае добавьте holder.image.setImageBitmap (null); after holder = (ViewHolder) convertView.getTag ();
Итак, ваш метод getView() будет выглядеть так:
@Override public View getView (конечная позиция int, View convertView, ViewGroup parent) {
...
if (convertView == null) {
LayoutInflater inflater = getLayoutInflater();
convertView = inflater.inflate(viewResourceId, null);
holder = new ViewHolder(convertView);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
holder.image.setImageBitmap(null)
}
...
return convertView;
}
Изменить: у меня была похожая проблема, изображения, используемые для быстрого изменения. Это происходит потому, что
Метод getView() адаптера List вызывается несколько раз. каждый раз, когда вызывается getView(), вы пытаетесь загрузить и установить изображение в строке. К тому времени, когда изображение загружается из сети, строка в списке, которая запрашивала это изображение, могла перемещаться за пределы видимой части экрана, но адаптер пытается повторно использовать эту строку, и это приводит к установке ранее запрошенного изображения в новой строке в этой позиции. (ранее запрошенная позиция строки).
Попробуйте этот подход в адаптере, установите запрошенный URL в ImageView с помощью setTag:
if(item.get_referenceImage().length()!=0){
//with view, hold the url. This is used to verify that right image is loaded on download completion
holder.refImageView.setTag(item.get_referenceImage());
imageManager.loadImage(new MetaItem(item.get_referenceImage(), holder.refImageView));
}else{
//no image, set default
Log.d(TAG, "No images found for the view setting default image");
holder.refImageView.setTag(null); //this is req, otherwise image cache will retain previous image
holder.refImageView.setImageResource(R.drawable.blank_96_1382x);
}
в кэше, перед загрузкой растрового изображения, проверьте, загружено ли правильное изображение:
String url = (String) m_imageViewRef.get().getTag();
if(url != null && url.compareTo(m_metaItem.m_url) == 0 ){
m_metaItem.m_imageViewRef.get().setImageBitmap(result);
}
PS: MetaItem - это просто POJO для хранения ссылки на imageView и URL изображения
Вы можете звонить adapter.notifyDatasetChanged()
что приводит к listview
перезагрузить его список элементов. попробуй не звонить adapter.notifyDatasetChanged()
ваш список должен автоматически обновляться при прокрутке. но это может привести к ArrayIndexOutOfBounds
исключение при прокрутке.
Решение состоит в том, чтобы не перезагружать ваше изображение, когда оно не изменилось.
В ваших адаптерах getView() сделайте:
// schedule rendering:
final String path = ... (set path here);
if (holder.lastImageUrl == null || !holder.lastImageUrl.equals(path)
|| holder.headerImageView.getDrawable() == null) {
// refresh image
imageLoader.displayImage(imageUri, imageAware);
} else {
// do nothing, image did not change and does not need to be updated
}
в случае успеха (добавьте ImageLoadingListener) вы устанавливаете holder.lastImageUrl = путь, в случае неудачи и отмены - установите для holder.lastImageUrl значение null, чтобы он перезагрузился в следующий раз.