使用 AsyncTask 在 ListView 中加载图片
Posted
技术标签:
【中文标题】使用 AsyncTask 在 ListView 中加载图片【英文标题】:Using AsyncTask to load Images in ListView 【发布时间】:2011-12-05 11:03:06 【问题描述】:我有一个可以容纳图像的 ListView。这取决于 SDCARD 中是否存在图像。
这是我的示例代码:
public class MainActivity extends Activity
ListView mListView;
@Override
protected void onCreate(Bundle savedInstanceState)
super.onCreate(savedInstanceState);
mListView = new ListView(this);
setContentView(mListView);
String[] arr = new String[]
"/example/images/1.jpg", "/example/images/2.jpg",
"/example/images/3.jpg", "/example/images/4.jpg",
"/example/images/5.jpg", "/example/images/6.jpg",
"/example/images/7.jpg", "/example/images/8.jpg",
"/example/images/9.jpg", "/example/images/1.jpg",
"/example/images/2.jpg", "/example/images/3.jpg",
"/example/images/4.jpg", "/example/images/5.jpg",
"/example/images/6.jpg", "/example/images/7.jpg",
"/example/images/8.jpg", "/example/images/9.jpg",
"/example/images/1.jpg", "/example/images/2.jpg",
"/example/images/3.jpg", "/example/images/4.jpg",
"/example/images/5.jpg", "/example/images/6.jpg",
"/example/images/7.jpg", "/example/images/8.jpg",
"/example/images/9.jpg", "/example/images/1.jpg",
"/example/images/2.jpg", "/example/images/3.jpg",
"/example/images/4.jpg", "/example/images/5.jpg",
"/example/images/6.jpg", "/example/images/7.jpg",
"/example/images/8.jpg", "/example/images/9.jpg";
List<String> list = Arrays.asList(arr);
MyAdapter adapter = new MyAdapter(this, R.layout.listitem_imv, list);
mListView.setAdapter(adapter);
class MyAdapter extends ArrayAdapter<String>
List<String> mList;
LayoutInflater mInflater;
int mResource;
public MyAdapter(Context context, int resource,
List<String> objects)
super(context, resource, objects);
mResource = resource;
mInflater = getLayoutInflater();
mList = objects;
@Override
public View getView(int position, View convertView, ViewGroup parent)
View view;
if(convertView == null)
view = mInflater.inflate(mResource, null);
else
view = convertView;
ImageView imageView = (ImageView) view.findViewById(R.id.imv);
TextView textView = (TextView) view.findViewById(R.id.txv);
imageView.setTag(mList.get(position));//tag of imageView == path to image
new LoadImage().execute(imageView);
textView.setText(mList.get(position).toString());
return view;
class LoadImage extends AsyncTask<Object, Void, Bitmap>
private ImageView imv;
private String path;
@Override
protected Bitmap doInBackground(Object... params)
imv = (ImageView) params[0];
path = imv.getTag().toString();
Bitmap bitmap = null;
File file = new File(
Environment.getExternalStorageDirectory().getAbsolutePath() + path);
if(file.exists())
bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
return bitmap;
@Override
protected void onPostExecute(Bitmap result)
if(result != null && imv != null)
imv.setVisibility(View.VISIBLE);
imv.setImageBitmap(result);
else
imv.setVisibility(View.GONE);
'sdcard / example / images'目录具有图像:1.jpg,2.jpg,3.jpg,4.jpg,6.jpg,7.jpg和9.jpg。 预期结果是:
但是,如果我快速滚动列表,则会在错误的项目中插入某些图像。 这是由于在 getView() 方法中使用了 convertView。
如果我使用以下代码,代码可以正常工作:
//if(convertView == null)
// view = mInflater.inflate(mResource, null);
//else
// view = convertView;
//
view = mInflater.inflate(mResource, null);
当列表滚动快速时,由于使用转换视图,两个asynctasks可以引用一个相同的视图。 如何在视图不再可见时取消ASYNCTASK?(并且是由另一个列表视图项目的使用)
编辑
@Override
protected void onPostExecute(Bitmap result)
if(result != null && imv != null)
if(imv.getTag().equals(path))
imv.setVisibility(View.VISIBLE);
imv.setImageBitmap(result);
else
imv.setVisibility(View.GONE);
else
imv.setVisibility(View.GONE);
【问题讨论】:
【参考方案1】:您可以将 ImageView 发送到任务构造函数并在那里保留对图像路径的引用。现在在 onPostExecute,检查 ImageView 的当前标签是否与您开始使用的标签相同。如果是,则设置图像。如果没有,不要做任何事情。
但是,这意味着无论如何都会下载图像。您不会在视图上设置错误的图像。
编辑: 首先将 ImageView 传递给任务构造函数:
new LoadImage(imageView).execute()
然后在 LoadImage 构造函数中保存对 ImageView 和图像路径的引用。将路径保存在构造函数中而不是 doInBackground 中很重要,以确保我们不会遇到多线程问题。然后在 onPostExecute 我们检查当前路径。
class LoadImage extends AsyncTask<Object, Void, Bitmap>
private ImageView imv;
private String path;
public LoadImage(ImageView imv)
this.imv = imv;
this.path = imv.getTag().toString();
@Override
protected Bitmap doInBackground(Object... params)
Bitmap bitmap = null;
File file = new File(
Environment.getExternalStorageDirectory().getAbsolutePath() + path);
if(file.exists())
bitmap = BitmapFactory.decodeFile(file.getAbsolutePath());
return bitmap;
@Override
protected void onPostExecute(Bitmap result)
if (!imv.getTag().toString().equals(path))
/* The path is not same. This means that this
image view is handled by some other async task.
We don't do anything and return. */
return;
if(result != null && imv != null)
imv.setVisibility(View.VISIBLE);
imv.setImageBitmap(result);
else
imv.setVisibility(View.GONE);
【讨论】:
我试过这个。见编辑。但是图像仍然被放入错误的项目中。 @Rodrigo 另外,如果您在检查是否正确之前缓存了下载的图像,这意味着它不会被浪费,因为它可以用于您的其他任务 是的@Droider,但请查看发布日期。 @shabyasachi 这个解决方案不正确,我不明白赞成票。 ListView 不是这样工作的,它总是使用相同的视图项目来显示内容,并且在滚动时,它使用回收的视图来显示新项目。该解决方案完全错误。 @DídacPérez:查看 onPostExecute 中的大评论。该评论解释了如何处理视图回收。【参考方案2】:android Developers Blog post 将为您提供一个完整的参考项目,包括缓存。只需将 Http 访问代码替换为 SD 卡文件读取即可。
【讨论】:
【参考方案3】:我希望这会有所帮助。 经过大量搜索后,我有了这个可行的解决方案。
public class CustomAdapter extends ArrayAdapter<String>
/*
public CustomAdapter(Context context , String[] video)
super(context,R.layout.custom_row, video);
*/
private final Activity context;
private final String[] video;
static class ViewHolder
public TextView videoTitle;
public ImageView videoThumbnail;
public int position;
public String path;
public CustomAdapter(Activity context, String[] video)
super(context, R.layout.custom_row, video);
this.context = context;
this.video = video;
@Override
public View getView(final int position, View convertView, ViewGroup parent)
LayoutInflater videoInflator = LayoutInflater.from(getContext());
View customView = videoInflator.inflate(R.layout.custom_row, parent, false);
ViewHolder viewHolder = new ViewHolder();
viewHolder.position = position;
viewHolder.path = video[position];
viewHolder.videoTitle = (TextView) customView.findViewById(R.id.videoTitle);
viewHolder.videoThumbnail = (ImageView) customView.findViewById(R.id.videoThumbnail);
//rowView.setTag(viewHolder);
//
customView.setTag(viewHolder);
final String videoItem = video[position];
int index=videoItem.lastIndexOf('/');
String lastString=(videoItem.substring(index +1));
index = lastString.indexOf(".mp4");
lastString=(lastString.substring(0,index));
viewHolder.videoTitle.setText(lastString);
new AsyncTask<ViewHolder, Void, Bitmap>()
private ViewHolder v;
@Override
protected Bitmap doInBackground(ViewHolder... params)
v = params[0];
Bitmap thumb = ThumbnailUtils.createVideoThumbnail(videoItem, MediaStore.Images.Thumbnails.MINI_KIND);
return thumb;
@Override
protected void onPostExecute(Bitmap result)
super.onPostExecute(result);
if (v.position == position)
// If this item hasn't been recycled already, hide the
// progress and set and show the image
v.videoThumbnail.setImageBitmap(result);
.execute(viewHolder);
return customView;
【讨论】:
【参考方案4】:也许你应该试试:
view = mInflater.inflate(mResource,parent,null);
查看此博客,它解释了类似的问题:
http://www.doubleencore.com/2013/05/layout-inflation-as-intended/
【讨论】:
【参考方案5】:我会做什么(除非您有数千张图片): 1. 创建一个数据结构 - 一个简单的类,包含一个要显示的字符串名称和一个位图 2.为它创建一个适配器 3.在getView方法中将正确的位图分配给正确的ImageView。
在您的情况下,尽管您可以创建一个类似的数据结构,但保存的不是位图而是 AsyncTask。无论如何,您需要将 asynctask 绑定到字符串中的一项。此类项目的数组(或数组列表)将被提供给您的适配器。将显示一个 imageview 和一个 textview。
AsyncTask 可以用 cancel() 取消。
【讨论】:
【参考方案6】:嘿,我找到了解决这个问题的方法,只需使用以下函数而不是你的函数
@Override
protected void onPostExecute(Bitmap result)
if (!imv.getTag().toString().equals(rec_id))
return;
if(result != null && imv != null)
int index = id.indexOf(imv.getTag().toString());
if(list.getFirstVisiblePosition()<=index && index<=list.getLastVisiblePosition())
imv.setVisibility(View.VISIBLE);
imv.setImageBitmap(result);
else
imv.setImageBitmap(icon);
imv.setVisibility(View.GONE);
这里的list是listview的对象。只需将列表视图对象传递给适配器并粘贴此函数而不是 onPostExecute 函数。
【讨论】:
以上是关于使用 AsyncTask 在 ListView 中加载图片的主要内容,如果未能解决你的问题,请参考以下文章
使用 asynctask 在所有 Fragment 中的 Activity 中显示 ListView
JSONArray 到 ListView - AsyncTask
AsyncTask 在 doInBackground 中崩溃,添加到 listview