Использование AsyncTask для загрузки изображений в ListView
У меня есть один 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. ожидаемый результат:
Но, если я прокручиваю список быстро, некоторые изображения вставляются не в те элементы. Это происходит из-за использования convertView в методе getView().
Если я использую следующий код, код работает нормально:
//if(convertView == null){
// view = mInflater.inflate(mResource, null);
//}else{
// view = convertView;
//}
view = mInflater.inflate(mResource, null);
Когда список прокручивается быстро, две асинхронные задачи могут ссылаться на одно и то же представление, благодаря использованию convertView. Как я могу отменить AsyncTask, когда представление больше не видно?(И используется другим элементом ListView)
редактировать
@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);
}
}
6 ответов
Вы можете отправить в ImageView конструктору задач и сохранить там ссылку на путь к изображению. Теперь в onPostExecute проверьте, совпадает ли текущий тег ImageView с тем, с которого вы начали. Если да, то установите изображение. Если нет, не делай ничего.
Однако это означает, что изображение будет загружено в любом случае. Вы просто не установите неправильное изображение на виде.
РЕДАКТИРОВАТЬ: сначала передать ImageView в конструктор задач:
new LoadImage(imageView).execute()
Затем сохраните ссылку на ImageView и путь к изображению в конструкторе LoadImage. Важно сохранить путь в конструкторе, а не в 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);
}
}
}
Это сообщение в блоге разработчиков Android предоставит вам полный справочный проект для этого с кэшированием. Просто замените Http-код доступа чтением файла с SD-карты.
Надеюсь, это поможет. После долгих поисков у меня есть это рабочее решение.
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;
}
}
Может быть, вы должны попробовать:
view = mInflater.inflate(mResource,parent,null);
Проверьте этот блог, это объясняет похожую проблему:
http://www.doubleencore.com/2013/05/layout-inflation-as-intended/
Эй, я нашел решение этой проблемы, просто используйте следующую функцию вместо вашей функции
@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.
Что бы я сделал (если у вас нет тысяч изображений): 1. создайте структуру данных - простой класс, содержащий отображаемое имя String и растровое изображение 2. создайте для него адаптер 3. в методе getView назначьте правильное растровое изображение к правильному ImageView.
В вашем случае вы можете создать похожую структуру данных, но не с растровым изображением, а с AsyncTask. В любом случае вам нужно связать асинктаску со строкой в один элемент. Массив (или массив) таких элементов будет подан на ваш адаптер. Отображается будет просмотр изображений и просмотр текста.
AsyncTask можно отменить с помощью cancel().