【问题标题】:Images are repeating in ListView图像在 ListView 中重复
【发布时间】:2020-05-08 03:41:20
【问题描述】:

我已经实现了应该从服务器下载图像并在 ListView 中显示它们的 android 应用程序,但是在图像下载时会发生非常有趣的事情

正如您在视频中看到的,尚未下载的图片由已下载的图片表示。这怎么可能发生?我已经考虑了差不多两天了。

http://www.youtube.com/watch?v=lxY-HAuJO0o&feature=youtu.be

这是我的 ListView 适配器代码。

public class MoviesAdapter extends ArrayAdapter<ParkCinema> {
        private ArrayList<ParkCinema> movieDataItems;   
        private Activity context;

        public MoviesAdapter(Activity context, int textViewResourceId, ArrayList<ParkCinema> movieDataItems) {
            super(context, textViewResourceId, movieDataItems);
            this.context = context;
            this.movieDataItems = movieDataItems;
        }

        @Override
        public View getView(int position, View convertView, ViewGroup parent) { 
            if (convertView == null) {
                LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                convertView = vi.inflate(R.layout.movie_data_row, null);
                }

            ParkCinema movie = movieDataItems.get(position);

            if (movie!=null){
                        ImageView imageView = (ImageView) convertView.findViewById(R.id.movie_thumb_icon);
                        String url = movie.poster();

                         if (url!=null) {
                            Bitmap bitmap = fetchBitmapFromCache(url);
                            if (bitmap==null) { 
                                new BitmapDownloaderTask(imageView).execute(url);
                            }
                            else {
                                imageView.setImageBitmap(bitmap);
                            } 
                        } 
            }
            return convertView;
        }

        private LinkedHashMap<String, Bitmap> bitmapCache = new LinkedHashMap<String, Bitmap>();

        private void addBitmapToCache(String url, Bitmap bitmap) {
            if (bitmap != null) {
                synchronized (bitmapCache) {
                    bitmapCache.put(url, bitmap);
                }
            }
        }

        private Bitmap fetchBitmapFromCache(String url) {

            synchronized (bitmapCache) {
                final Bitmap bitmap = bitmapCache.get(url);
                 if (bitmap != null) {
                    return bitmap;
                } 
            }

            return null;

        }


    private class BitmapDownloaderTask extends AsyncTask<String, Void, Bitmap> {

            private String url;
            private final WeakReference<ImageView> imageViewReference;

            public BitmapDownloaderTask(ImageView imageView) {
                imageViewReference = new WeakReference<ImageView>(imageView);
            }

            @Override
            protected Bitmap doInBackground (String... source) {
                url = source[0];
                Bitmap image;
                try{
                    image = BitmapFactory.decodeStream(new URL(url).openConnection().getInputStream());
                    return image;
                    }
                catch(Exception e){Log.e("Error", e.getMessage()); e.printStackTrace();}
                return null;
                } 


            @Override
            protected void onPostExecute(Bitmap bitmap) {       
                addBitmapToCache(url, bitmap);
                imageViewReference.get().setImageBitmap(bitmap);               
            }
        }
    }

编辑 3:

public View getView(int position, View convertView, ViewGroup parent) { 
    if (convertView == null) {
        LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 
        convertView = vi.inflate(R.layout.movie_data_row, null);
        }
    ParkCinema movie = movieDataItems.get(position);
    ImageView imageView = (ImageView) convertView.findViewById(R.id.movie_thumb_icon);
    if (movie!=null){
                String url = movie.poster();

                    if (url != null) {
                        Bitmap bitmap = fetchBitmapFromCache(url);
                        if (bitmap == null) {
                            imageView.setImageResource(R.drawable.no_image);
                            new BitmapDownloaderTask(imageView).execute(url);
                        }
                        else {
                            imageView.setImageBitmap(bitmap);
                        }
                    }
                    else {
                        imageView.setImageResource(R.drawable.no_image);
                    }
                }
                else {
                    imageView.setImageResource(R.drawable.no_image);
                } 

    return convertView;

}

【问题讨论】:

  • ImageView imageView = (ImageView) convertView.findViewById(R.id.movie_thumb_icon);之后,添加imageView.setImageBitmap(null);。您看到的是正常行为
  • 感谢您的回答。如果我将 null 设置为 imageView 会发生同样的事情
  • 对不起!使用imageView.setImageDrawable(null)imageView.setImageResource(0)
  • 尝试了两种变体,没有帮助:(
  • 您是否将其添加到正确的位置?就在ImageView imageView = (ImageView) convertView.findViewById(R.id.movie_thumb_icon);之后

标签: android android-layout


【解决方案1】:

啊哈!我想我可能知道这个问题。现在,您的 getView 方法将您的 ImageView 设置为:

  1. 获取电影对象的位置
  2. 拉出电影的缩略图网址
  3. 使用该 url,它会尝试在缓存中查找图像
  4. 如果找到图像,则设置它
  5. 如果找不到图像,它会启动一个异步网络请求去获取它,并在下载后设置它。

您的问题出现是因为ListView 重用了其行的Views。当第一个 View 滚动到屏幕外而不是膨胀一个新的时,ListView 将现在屏幕外行的 View 传递为 convertView 供您重复使用(这是为了提高效率)。

当您的getView 获得一个正在重复使用的convertView 时,它的ImageView 已经从之前拥有它的行中设置,因此您可以从屏幕外行的View 中看到旧图像。使用您当前的getView 进程,您检查新行的图像,但它在缓存中找不到它,它开始请求下载它。在下载过程中,您会看到旧图像,直到获得新图像。

要解决此问题,您需要确保立即设置行的View 中的每个字段,以确保您没有任何Views 显示陈旧数据。我建议您在等待网络下载获取图像时将ImageView 设置为默认的drawable 资源(您已在R.layout.movie_data_row 中设置)。

@Override
public View getView(int position, View convertView, ViewGroup parent) {
    if (convertView == null) {
        LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        convertView = vi.inflate(R.layout.movie_data_row, null);
    }

    ParkCinema movie = movieDataItems.get(position);
    ImageView imageView = (ImageView) convertView.findViewById(R.id.movie_thumb_icon);
    if (movie != null) {
        String url = movie.poster();

        if (url != null) {
            Bitmap bitmap = fetchBitmapFromCache(url);
            if (bitmap == null) {
                // Set the movie thumbnail to the default icon while we load
                // the real image
                imageView.setImageResource(R.drawable.movie_thumb_icon);
                new BitmapDownloaderTask(imageView).execute(url);
            }
            else {
                // Set the image to the bitmap we get from the cache
                imageView.setImageBitmap(bitmap);
            }
        }
        else {
            // Set the movie thumbnail to the default icon, since it doesn't
            // have a thumbnail URL
            imageView.setImageResource(R.drawable.movie_thumb_icon);
        }
    }
    else {
        // Set the movie thumbnail to the default icon, since there's no
        // movie data for this row
        imageView.setImageResource(R.drawable.movie_thumb_icon);
    }

-编辑-

使用您的drawable 更新为更加强大。您的BitmapDownloaderTask 也有问题,它不处理错误/空值。也尝试添加它。

@Override
protected void onPostExecute(Bitmap bitmap) { 
    addBitmapToCache(url, bitmap);
    if (bitmap == null) {
        // Set the movie thumbnail to the default icon, since an error occurred while downloading
        imageViewReference.get().setImageResource(R.drawable.movie_thumb_icon);
    }
    else {
        imageViewReference.get().setImageBitmap(bitmap);
    }            
}

【讨论】:

  • 感谢您如此详细的回复。正如你所说,我已经修改了代码,但同样的事情发生了:(我认为 ListView 将 convertView 设置为新的行对象并在到达我们现在修改的代码部分之前显示它,所以它显示旧图像。
  • 我已经实施了 Pramod J George 建议的方法,它解决了我的问题,但又创建了另一个问题。现在应用程序在模拟器中运行速度较慢,我不喜欢它。只想让一切变得完美:/
  • Pramod J George 的建议是 hack,这就像砸墙与使用门一样。使用该方法滚动时会出现延迟,因为它没有重用Views。您可以编辑问题并显示您尝试使用我的方法的代码吗?我感觉可能少了一小块。
  • 完成!此外,我从这里查找了两个示例 - stackoverflow.com/questions/541966/… 在这两个示例中,图像都在毫无延迟地下载。我已经阅读了源代码,我认为他们使用的适配器实现与我的几乎相同,但他们的代码运行良好:/
  • 您需要完全复制我的方法,您并没有完全实现我第一次建议的内容。请复制我的方法并粘贴到您的方法上,如果它们不起作用,请解释它的行为方式。我可以在我的应用程序中正常加载这样的图像,所以你正在做的事情必须关闭。
【解决方案2】:

我遇到了这个问题并实现了 lruCache ...我相信您需要 api 12 及更高版本或使用兼容性 v4 库。 lurCache 是快速内存,但它也有预算,所以如果您担心可以使用磁盘缓存...这里都描述了 http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

我现在将提供我的实现,它是我从任何地方调用的单例,如下所示:

//第一个是字符串,另一个是要加载的图像视图

DownloadImageTask.getInstance().loadBitmap(avatarURL, iv_avatar); 

这是理想的缓存代码,然后在检索网络图像时在适配器的 getView 中调用上述代码:

 public class DownloadImageTask {

private LruCache<String, Bitmap> mMemoryCache;

/* create a singleton class to call this from multiple classes */

private static DownloadImageTask instance = null;

public static DownloadImageTask getInstance() {
    if (instance == null) {
        instance = new DownloadImageTask();
    }
    return instance;
}

//lock the constructor from public instances
private DownloadImageTask() {

    // Get max available VM memory, exceeding this amount will throw an
    // OutOfMemory exception. Stored in kilobytes as LruCache takes an
    // int in its constructor.
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);

    // Use 1/8th of the available memory for this memory cache.
    final int cacheSize = maxMemory / 8;

    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
        @Override
        protected int sizeOf(String key, Bitmap bitmap) {
            // The cache size will be measured in kilobytes rather than
            // number of items.
            return bitmap.getByteCount() / 1024;
        }
    };

}

public void loadBitmap(String avatarURL, ImageView imageView) {
    final String imageKey = String.valueOf(avatarURL);

    final Bitmap bitmap = getBitmapFromMemCache(imageKey);
    if (bitmap != null) {
        imageView.setImageBitmap(bitmap);
    } else {
        imageView.setImageResource(R.drawable.ic_launcher);

        new DownloadImageTaskViaWeb(imageView).execute(avatarURL);
    }
}

private void addBitmapToMemoryCache(String key, Bitmap bitmap) {
    if (getBitmapFromMemCache(key) == null) {
        mMemoryCache.put(key, bitmap);
    }
}

private Bitmap getBitmapFromMemCache(String key) {
    return mMemoryCache.get(key);
}

/* a background process that opens a http stream and decodes a web image. */

class DownloadImageTaskViaWeb extends AsyncTask<String, Void, Bitmap> {
    ImageView bmImage;

    public DownloadImageTaskViaWeb(ImageView bmImage) {
        this.bmImage = bmImage;
    }

    protected Bitmap doInBackground(String... urls) {

        String urldisplay = urls[0];
        Bitmap mIcon = null;
        try {
            InputStream in = new java.net.URL(urldisplay).openStream();
            mIcon = BitmapFactory.decodeStream(in);

        } catch (Exception e) {
            Log.e("Error", e.getMessage());
            e.printStackTrace();
        }

        addBitmapToMemoryCache(String.valueOf(urldisplay), mIcon);

        return mIcon;
    }

    /* after decoding we update the view on the mainUI */
    protected void onPostExecute(Bitmap result) {
        bmImage.setImageBitmap(result);

    }

}

}

【讨论】:

    【解决方案3】:

    视图被重用以提高适配器的性能。您应该使用另一种方法。 你必须有一个重用你的观点的班级持有者。在您的情况下,您的课程应该是这样的:

       public class MoviesAdapter extends ArrayAdapter<ParkCinema> {
        private ArrayList<ParkCinema> movieDataItems;   
        private Activity context;
    
        public MoviesAdapter(Activity context, int textViewResourceId,       ArrayList<ParkCinema> movieDataItems) {
            super(context, textViewResourceId, movieDataItems);
            this.context = context;
            this.movieDataItems = movieDataItems;
        }
    
        @Override
        public View getView(int position, View convertView, ViewGroup parent) { 
            if (convertView == null) {
                LayoutInflater vi = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
                convertView = vi.inflate(R.layout.movie_data_row, null);
    
                    holder = new ViewHolder();
    
                    holder.imageView = (BarImageView) convertView.findViewById(R.id.movie_thumb_icon);
                  } else {
                            holder = (ViewHolder) convertView.getTag();
    
                      }
    
            ParkCinema movie = movieDataItems.get(position);
    
            if (movie!=null){
    
                        String url = movie.poster();
    
                         if (url!=null) {
                            Bitmap bitmap = fetchBitmapFromCache(url);
                            if (bitmap==null) { 
                                new BitmapDownloaderTask(imageView).execute(url);
                            }
                            else {
                                imageView.setImageBitmap(bitmap);
                            } 
                        } 
            }
            return convertView;
        }
    
        private LinkedHashMap<String, Bitmap> bitmapCache = new LinkedHashMap<String, Bitmap>();
    
        private void addBitmapToCache(String url, Bitmap bitmap) {
            if (bitmap != null) {
                synchronized (bitmapCache) {
                    bitmapCache.put(url, bitmap);
                }
            }
        }
    
        private Bitmap fetchBitmapFromCache(String url) {
    
            synchronized (bitmapCache) {
                final Bitmap bitmap = bitmapCache.get(url);
                 if (bitmap != null) {
                    return bitmap;
                } 
            }
    
            return null;
    
    
    public static class ViewHolder {
    
    
            ImageView imageView; 
    
    
        }
    
        }
    

    【讨论】:

    • 第一次调用 getView 时,您的视图被初始化(在 if (convertView == null) { 内部)。然后,对于下一行,视图不再实例化,而是重用。这就是您复制图像的原因。
    • 在这种情况下,viewholder 会做什么?它为每一行创建新的 ImageView 对象?
    • viewholder 保存为第一行创建的对象
    • 抱歉,我没听明白。在我的代码中,ImageView 对象被重用于除第一行之外的所有行,但如果我使用 ViewHolder,它会改变什么吗?
    【解决方案4】:

    我也花了几个小时试图解决这个问题...感谢 Steven Byle 的解决方案... 当用户从列表中选择一个项目时,这是我对类似问题的解决方案:

    adapter.setSelectedIndex(position);
    

    然后在自定义适配器中:

    public void setSelectedIndex(int ind)
    {
        selectedIndex = ind;
        notifyDataSetChanged();
    }
    

    然后最后在适配器的getView方法中:

    if(selectedIndex!= -1 && position == selectedIndex)
             {
                 holder.tab.setBackgroundColor(Color.BLACK);
             }
             else{
                 holder.tab.setBackgroundColor(Color.DKGRAY);
             }
    

    因此,总而言之,请确保您分配默认值

    【讨论】:

      【解决方案5】:

      在我的例子中,我使用 Picasso 库而不是 AsyncTask 来下载图像。 enter link description here

      还要写 if else 条件,即如果 url 不可用则设置为 null 为 image

      【讨论】:

      • 欢迎来到 SO。您应该在此处添加图像而不是链接到它们。如果链接停止工作,则答案将丢失。
      【解决方案6】:

      每次都创建一个新视图,而不是使用 convertview 对象。

      View localView = ((LayoutInflater)parentscreen.getSystemService("layout_inflater")).inflate(R.layout.activity_list_row, null);
      

      通过上述膨胀。

      【讨论】:

      • 这不会消耗很多内存吗?
      • 尝试了您的解决方案,但没有任何改变。也许我做错了什么,你能发布更详细的例子吗?
      • 每次都增加行布局是不好的做法。 ListView 重用行布局并将它们作为convertView 传递。他的那部分代码是正确的,问题与他的膨胀代码无关。
      • 你能解释一下为什么这是不好的做法吗?还有其他解决办法吗?
      猜你喜欢
      • 1970-01-01
      • 2015-07-30
      • 1970-01-01
      • 2014-06-19
      • 2011-02-04
      • 1970-01-01
      • 1970-01-01
      • 2013-10-21
      • 1970-01-01
      相关资源
      最近更新 更多