Использование 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.
ожидаемый результат:
![example]()
Но, если я быстро прокручу список, некоторые изображения вставлены в неправильные элементы.
Это происходит из-за использования метода convertView в методе getView().
Если я использую следующий код, код работает нормально:
//if(convertView == null){
// view = mInflater.inflate(mResource, null);
//}else{
// view = convertView;
//}
view = mInflater.inflate(mResource, null);
Когда список прокручивается быстро, две asyncTasks могут ссылаться на один вид View из-за использования convertView.
Как я могу отменить AsyncTask, когда вид больше не виден? (И является useb другим элементом 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);
}
}
Ответы
Ответ 1
Вы можете отправить ImageView в конструктор задачи и сохранить ссылку на путь изображения. Теперь в onPostExecute проверьте, совпадает ли текущий тег ImageView с тем, с которого вы начали работать. Если да, то установите изображение. Если нет, ничего не делайте.
Однако это означает, что изображение будет загружено в любом случае. Вы просто не установите неправильное изображение в представлении.
EDIT:
Сначала передайте 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);
}
}
}
Ответ 2
Это сообщение Android Developers Blog даст вам полный справочный проект для этого завершения с кешированием. Просто замените код доступа 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
Что бы я сделал (если у вас нет тысяч изображений):
1. создать структуру данных - простой класс, содержащий отображаемое имя строки, и растровое изображение
2. создать для него адаптер
3. в методе getView назначьте правильное растровое изображение правильному ImageView.
В вашем случае, хотя вы можете создать подобную структуру данных, но не сохранить растровое изображение, а AsyncTask. В любом случае вам нужно привязать асинтез к строке в один элемент. Массив (или arraylist) таких предметов будет передан вашему адаптеру. Отобразится изображение и текстовое изображение.
AsyncTask можно отменить с помощью cancel().
Ответ 5
Привет, я нашел решение этой проблемы, просто использую следующую функцию вместо вашей функции
@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);
}
}
Здесь список - это объект listview. Просто передайте объект просмотра списка вашему адаптеру и вставьте эту функцию вместо своей функции onPostExecute.
Ответ 6
Возможно, вам стоит попробовать:
view = mInflater.inflate(mResource,parent,null);
В этом блоге объясняется аналогичная проблема:
http://www.doubleencore.com/2013/05/layout-inflation-as-intended/