我已经实现了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;
}
答案 0 :(得分:20)
啊哈!我想我可能知道这个问题。现在,您的getView
方法会将ImageView
设置为:
自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);
通过如上所述膨胀。