安卓:在添加数据到ArrayAdapter时,可见的ListView图片闪烁

9
我有一个自定义适配器,它扩展了ArrayAdapter,并实现了视图持有者模式来显示来自Web服务的数据(文本+图像)。
为了懒加载图像,我使用Android开发人员网站上的高级培训中的异步任务模式,
我还使用磁盘+RAM位图缓存。
当需要检索其他数据时,我添加一个页脚视图,单击该视图从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 adapter包装我的适配器,并且两者都得到了相同的结果... 这只剩下ImageDownloader类和我的适配器作为可能的原因... 我完全迷失了。

4个回答

4
这个闪烁的原因是,listview中的列表项会被重复使用。当它们被重用时,列表项中的imageviews保留了先前显示的旧图像引用,后来下载新图像后才开始显示,这导致了闪烁现象。 为了避免这种闪烁问题,在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;
}

1

编辑: 我遇到了类似的问题,图片会快速更换。这是因为List adapter的getView()方法被多次调用。 每次调用getView()时,您都会尝试下载并在行上设置图像。当从网络下载图像时,请求该图像的列表中的行可能已经移出屏幕的可见部分,但适配器仍然尝试重用该行,这会导致在新位置(先前请求的行位置)上设置先前请求的图像。

尝试这种方法 在适配器中,使用setTag将请求的URL设置在ImageView上:

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 的引用。


0
解决方案是在图像未更改时不重新加载它。
在您的适配器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,以便下次重新加载。

0
你可能会调用adapter.notifyDatasetChanged(),这会导致listview重新加载其列表项。 尝试不要调用adapter.notifyDatasetChanged(),当你滚动时,你的listview应该会自动更新。 但是这可能会在滚动时引发ArrayIndexOutOfBounds异常。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接