【问题标题】:"Flickering images" with ViewHolder pattern when downloading images from server从服务器下载图像时带有 ViewHolder 模式的“闪烁图像”
【发布时间】:2015-09-10 00:37:43
【问题描述】:

我想创建一个视图列表,其中每个视图中显示的图像是在您滚动列表时从服务器下载的(延迟加载)。这是我目前得到的代码:

public class CustomAdapter extends ArrayAdapter<Component> {

    private final List<Component> components;
    private final Activity activity;

    public CustomAdapter(Activity context, List<Component> components) {
        super(context, android.R.layout.simple_list_item_1, components);
        this.components = components;
        this.activity = context;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {

        ViewHolder viewHolder;

        if (convertView == null) {
            LayoutInflater inflater = activity.getLayoutInflater();
            convertView = inflater.inflate(R.layout.item, null, false);

            viewHolder = new ViewHolder();
            viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);

            convertView.setTag(viewHolder);
        } else {
            viewHolder = (ViewHolder)convertView.getTag();
        }

        Component component = components.get(position);
        // Don't show any image before the correct one is downloaded
        viewHolder.imageView.setImageBitmap(null);

        if (component.getImage() == null) { // Image not downloaded already
            new DownloadImageTask(viewHolder.imageView).execute(component);
        } else {
            viewHolder.imageView.setImageBitmap(component.getImage());
        }

        return convertView;
    }

    private class ViewHolder {
        ImageView imageView;
    }

    private class DownloadImageTask extends AsyncTask<Component, Void, Component> {

        private ImageView imageView;

        public DownloadImageTask(ImageView imageView) {
            this.imageView = imageView;
        }

        @Override
        protected Component doInBackground(Component... params) {
            String url = params[0].getImageURL();
            Component component = params[0];
            // Download the image using the URL address inside found in component
            Bitmap image = ImageDownloader.getImage(url);
            // Set the Bitmap image to the component so we don't have to download it again
            component.setImage(image);
            return component;
        }

        @Override
        protected void onPostExecute(Component component) {
            // Update the ImageView with the downloaded image and play animation
            imageView.setImageBitmap(component.getImage());
            Animation animation = AnimationUtils.loadAnimation(activity, R.anim.fade_in);
            imageView.startAnimation(animation);
        }
    }
}

基本上,当 getView() 运行时,它会从组件(用于缓存项目的数据)中获取数据(在本例中为位图),除非没有数据。在这种情况下,它会执行 DownloadImageTask,它将下载图像并将其存储在组件中。存储后,它会将图像放入 ImageView。

我的问题是,当使用 ImageViews 的 ViewHolder 模式时,而不是“错误的方式”(每次调用 findViewById()),滚动列表会使错误的 ImageViews 获取下载的位图。这个 gif 显示了它的外观:

Preview

显然,我希望图像只出现在它们应该出现的地方。有什么好方法可以使这项工作按预期进行吗?

【问题讨论】:

  • 你怎么了?使用毕加索加载您的图像。为此使用异步任务绝对是愚蠢的。
  • 这是因为加载图像的异步特性以及ListView recyclers(阅读:重用)视图的事实。基本上,图像加载是针对某一行开始的,然后当您开始滚动仍然有正在进行的图像加载的行时,它会离开屏幕并被重新用于可视化一个新项目,而另一个图像加载将被启动。 “旧”图像加载现在完成并设置图像,然后“新”图像加载也完成并设置不同的图像。因此,“闪烁”。长话短说:帮自己一个忙,使用像 Picasso 或 Glide 这样的专用库。
  • 哦,还没有偶然发现 Picasso 或 Glide。一定会检查出来的,谢谢! @ViktorYakunin 我想缺乏知识可能是我的问题。 ;)
  • 有很多image loading libraries available for Android,其中大部分为您处理此类工作。无论您使用 Picasso、Glide、Universal Image Loader 还是其他任何一种,您都非常想使用其中一个。

标签: java android imageview android-viewholder


【解决方案1】:

我使用 Glide 解决了这个问题。感谢大家告诉我,已经存在如此美妙的事物!

做(几乎)同样的事情就像:

public View getView(int position, View convertView, ViewGroup parent) {
    ViewHolder viewHolder;
    if (convertView == null) {
        LayoutInflater inflater = activity.getLayoutInflater();
        convertView = inflater.inflate(R.layout.item, null, false);

        viewHolder = new ViewHolder();
        viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);

        convertView.setTag(viewHolder);
    } else {
        viewHolder = (ViewHolder) convertView.getTag();
    }

    String url = components.get(position).getImageURL();
    Glide.with(activity).load(url).crossFade().into(viewHolder.imageView);

    return convertView;
}

【讨论】:

    【解决方案2】:

    虽然其他人认为有许多库可以为您处理这个问题是正确的,但如果您真的愿意,也可以按照您最初尝试的方式进行操作。您只需要确保在回收项目视图时取消 AsyncTasks。您可以通过将 AsyncTask 添加到您的 ViewHolder 类来执行此操作,这样您就可以查看是否有某些东西正在运行并在开始新的之前取消它。

    private class ViewHolder {
        ImageView imageView;
        AsyncTask<?,?,?> task;
    }
    

    然后在你的 getView() 中:

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
    
        ...
    
        // Don't show any image before the correct one is downloaded
        viewHolder.imageView.setImageBitmap(null);
    
        if (viewHolder.task != null) { // Existing task may be executing
            viewHolder.task.cancel(true);
            viewHolder.task = null;
        }
    
        if (component.getImage() == null) { // Image not downloaded already
            AsyncTask<?,?,?> task = new DownloadImageTask(viewHolder.imageView);
            viewHolder.task = task;
            task.execute(component);
        } else {
            viewHolder.imageView.setImageBitmap(component.getImage());
        }
    
        return convertView;
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2010-10-29
      • 1970-01-01
      相关资源
      最近更新 更多