Android:将数据添加到 ArrayAdapter 时可见的 ListView 图像闪烁
Posted
技术标签:
【中文标题】Android:将数据添加到 ArrayAdapter 时可见的 ListView 图像闪烁【英文标题】:Android: visible ListView images flicker when adding data to ArrayAdapter 【发布时间】:2012-10-30 15:05:40 【问题描述】:我有一个扩展 ArrayAdapter 的自定义适配器,它实现了视图持有者模式以显示来自 Web 服务的数据(文本 + 图像)。
对于图像的延迟加载,我使用了 android 开发者网站高级培训中的异步任务模式,
我也使用磁盘+内存位图缓存。
当有额外的数据要检索时,我会添加一个页脚视图,点击它会从 Web 服务检索额外的数据并将其添加到适配器。
问题在于,当添加这些新数据时,一些可见图像会发生变化并立即变回,从而导致奇怪的闪烁。
除此之外一切正常,滚动流畅。
据我了解,这些图像更改是在添加新数据时刷新可见视图时发生的。
有没有办法绕过这种不受欢迎的行为?
这是执行下载和管理异步任务的类
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 扩展 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 的 Endless 适配器包装我的适配器,我得到了相同的结果..所以这只留下 ImageDownloader 类和我的适配器作为可能的原因..我完全迷失了。
【问题讨论】:
【参考方案1】:这种闪烁的原因是,在 listview 列表项被重用。重新使用时,列表项中的图像视图会保留最先显示的旧图像引用。稍后,一旦下载了新图像,它就会开始显示。这会导致闪烁行为。 为避免此闪烁问题,请在重用时始终从 imageview 中清除旧的图像引用。
在您的情况下,在 holder = (ViewHolder) convertView.getTag();
之后添加 holder.image.setImageBitmap(null);因此,您的 getView() 方法将如下所示:
@Override
public View getView(final int position, 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;
【讨论】:
【参考方案2】:编辑: 我有类似的问题,图像过去经常变化。发生这种情况是因为
List 适配器的getView() 方法被多次调用。 每次调用 getView() 时,您都会尝试下载并设置行上的图像。到从网络下载图像时,列表中请求该图像的行可能已移出屏幕的可见部分,但适配器尝试重用该行,这导致在该位置的新行上设置先前请求的图像(以前请求的行位置)。
试试这个方法 在适配器中,使用 setTag 在 ImageView 上设置请求的 URL:
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 的引用
【讨论】:
【参考方案3】:解决方案是在图像未更改时不重新加载。
在你的适配器 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 = path,失败时取消设置 holder.lastImageUrl 为 null,以便下次重新加载。
【讨论】:
【参考方案4】:您可能正在调用adapter.notifyDatasetChanged()
,这会导致listview
重新加载其列表项。
尽量不要打电话给adapter.notifyDatasetChanged()
当您滚动时,您的列表视图应自动更新。
但这可能会在滚动时导致ArrayIndexOutOfBounds
异常。
【讨论】:
以上是关于Android:将数据添加到 ArrayAdapter 时可见的 ListView 图像闪烁的主要内容,如果未能解决你的问题,请参考以下文章
将来自 Api 响应的数据添加到 Android 中的 Firebase 数据库中
将多个firebase项目添加到一个flutter(android部分)项目中
Android:将数据添加到 ArrayAdapter 时可见的 ListView 图像闪烁