图像在ListView中重复

时间:2013-03-14 15:00:50

标签: android android-layout

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

正如您在尚未下载的视频图片中看到的那样,已经下载的视频图片表示。怎么会这样?我差不多两天都在考虑它。

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;

}

6 个答案:

答案 0 :(得分:20)

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

  1. 获取位置
  2. 的电影对象
  3. 拉出电影的缩略图网址
  4. 使用该网址,它会尝试在缓存中找到图像
  5. 如果找到图像,则将其设置为
  6. 如果找不到图像,它会启动异步网络请求以获取它,并在下载后设置它。
  7. ListView重复使用其行View以来,您的问题就出现了。当第一个View滚出屏幕时,ListView会将当前屏幕外的行View作为convertView传递给您,而不是让新的getView通过,以便重复使用(这是为了效率)。

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

    要解决此问题,您需要确保立即设置行View中的每个字段,以确保没有任何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); } } 也有问题,它不处理错误/ null。尝试添加它。

    {{1}}

答案 1 :(得分:1)

我有这个问题并实现了lruCache ...我相信你需要api 12及以上或者使用兼容性v4库。 lurCache是​​快速的内存,但它也有预算,所以如果你担心你可以使用diskcache ...这里所描述的http://developer.android.com/training/displaying-bitmaps/cache-bitmap.html

我现在将提供我的实现,这是我在任何地方调用的单身人士:

//其中first是一个字符串,另一个是要加载的imageview

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

这是理想的缓存代码,然后在检索Web图像时在适配器的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);

    }

}

}

答案 2 :(得分:0)

使用适配器重用视图以提高性能。你应该使用另一个approch。 您必须拥有一个可以重复使用您的视图的类持有者。在你的情况下,你的课应该是这样的:

   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; 


    }

    }

答案 3 :(得分:0)

我花了好几个小时试图解决这个问题......感谢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);
         }

因此,最后确保您指定默认值

答案 4 :(得分:0)

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

同时写if else条件,如果url不可用则设置为null

答案 5 :(得分:-2)

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

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

通过如上所述膨胀。