"Flickering images" 从服务器下载图像时使用 ViewHolder 模式
"Flickering images" with ViewHolder pattern when downloading images from server
我想创建一个视图列表,其中每个视图中显示的图像都是在您滚动列表时从服务器下载的(延迟加载)。这是我目前得到的代码:
public class CustomAdapter extends ArrayAdapter<Component> {
private final List<Component> components;
private final Activity activity;
public CustomAdapter(Activity context, List<Component> components) {
super(context, android.R.layout.simple_list_item_1, components);
this.components = components;
this.activity = context;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
LayoutInflater inflater = activity.getLayoutInflater();
convertView = inflater.inflate(R.layout.item, null, false);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder)convertView.getTag();
}
Component component = components.get(position);
// Don't show any image before the correct one is downloaded
viewHolder.imageView.setImageBitmap(null);
if (component.getImage() == null) { // Image not downloaded already
new DownloadImageTask(viewHolder.imageView).execute(component);
} else {
viewHolder.imageView.setImageBitmap(component.getImage());
}
return convertView;
}
private class ViewHolder {
ImageView imageView;
}
private class DownloadImageTask extends AsyncTask<Component, Void, Component> {
private ImageView imageView;
public DownloadImageTask(ImageView imageView) {
this.imageView = imageView;
}
@Override
protected Component doInBackground(Component... params) {
String url = params[0].getImageURL();
Component component = params[0];
// Download the image using the URL address inside found in component
Bitmap image = ImageDownloader.getImage(url);
// Set the Bitmap image to the component so we don't have to download it again
component.setImage(image);
return component;
}
@Override
protected void onPostExecute(Component component) {
// Update the ImageView with the downloaded image and play animation
imageView.setImageBitmap(component.getImage());
Animation animation = AnimationUtils.loadAnimation(activity, R.anim.fade_in);
imageView.startAnimation(animation);
}
}
}
基本上,当 getView() 为 运行 时,它从组件(用于缓存项目中的数据)获取数据(在本例中为位图),除非没有数据。在这种情况下,它会执行 DownloadImageTask,它将下载图像并将其存储在组件中。存储后,它会将图像放入 ImageView。
我的问题是,当 ImageView 使用 ViewHolder 模式而不是 "wrong way"(每次调用 findViewById())时,滚动列表会使错误的 ImageView 获取下载的位图。此 gif 显示它的外观:
显然,我希望图像只出现在它们应该出现的地方。有什么好的方法可以使它按预期工作吗?
我用Glide解决了这个问题。谢谢大家告诉我原来有这么好的东西!
做(几乎)同样的事情就像:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
LayoutInflater inflater = activity.getLayoutInflater();
convertView = inflater.inflate(R.layout.item, null, false);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
String url = components.get(position).getImageURL();
Glide.with(activity).load(url).crossFade().into(viewHolder.imageView);
return convertView;
}
虽然其他人是对的,有许多库可以为您处理此问题,但如果您确实愿意,也可以按照您最初尝试的方式进行处理。您只需要确保在项目视图被回收时取消 AsyncTasks。您可以通过将 AsyncTask 添加到您的 ViewHolder class 来完成此操作,这样您就可以查看是否有内容 运行 并在开始新的之前取消它。
private class ViewHolder {
ImageView imageView;
AsyncTask<?,?,?> task;
}
然后在你的 getView() 中:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
...
// Don't show any image before the correct one is downloaded
viewHolder.imageView.setImageBitmap(null);
if (viewHolder.task != null) { // Existing task may be executing
viewHolder.task.cancel(true);
viewHolder.task = null;
}
if (component.getImage() == null) { // Image not downloaded already
AsyncTask<?,?,?> task = new DownloadImageTask(viewHolder.imageView);
viewHolder.task = task;
task.execute(component);
} else {
viewHolder.imageView.setImageBitmap(component.getImage());
}
return convertView;
}
我想创建一个视图列表,其中每个视图中显示的图像都是在您滚动列表时从服务器下载的(延迟加载)。这是我目前得到的代码:
public class CustomAdapter extends ArrayAdapter<Component> {
private final List<Component> components;
private final Activity activity;
public CustomAdapter(Activity context, List<Component> components) {
super(context, android.R.layout.simple_list_item_1, components);
this.components = components;
this.activity = context;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
LayoutInflater inflater = activity.getLayoutInflater();
convertView = inflater.inflate(R.layout.item, null, false);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder)convertView.getTag();
}
Component component = components.get(position);
// Don't show any image before the correct one is downloaded
viewHolder.imageView.setImageBitmap(null);
if (component.getImage() == null) { // Image not downloaded already
new DownloadImageTask(viewHolder.imageView).execute(component);
} else {
viewHolder.imageView.setImageBitmap(component.getImage());
}
return convertView;
}
private class ViewHolder {
ImageView imageView;
}
private class DownloadImageTask extends AsyncTask<Component, Void, Component> {
private ImageView imageView;
public DownloadImageTask(ImageView imageView) {
this.imageView = imageView;
}
@Override
protected Component doInBackground(Component... params) {
String url = params[0].getImageURL();
Component component = params[0];
// Download the image using the URL address inside found in component
Bitmap image = ImageDownloader.getImage(url);
// Set the Bitmap image to the component so we don't have to download it again
component.setImage(image);
return component;
}
@Override
protected void onPostExecute(Component component) {
// Update the ImageView with the downloaded image and play animation
imageView.setImageBitmap(component.getImage());
Animation animation = AnimationUtils.loadAnimation(activity, R.anim.fade_in);
imageView.startAnimation(animation);
}
}
}
基本上,当 getView() 为 运行 时,它从组件(用于缓存项目中的数据)获取数据(在本例中为位图),除非没有数据。在这种情况下,它会执行 DownloadImageTask,它将下载图像并将其存储在组件中。存储后,它会将图像放入 ImageView。
我的问题是,当 ImageView 使用 ViewHolder 模式而不是 "wrong way"(每次调用 findViewById())时,滚动列表会使错误的 ImageView 获取下载的位图。此 gif 显示它的外观:
显然,我希望图像只出现在它们应该出现的地方。有什么好的方法可以使它按预期工作吗?
我用Glide解决了这个问题。谢谢大家告诉我原来有这么好的东西!
做(几乎)同样的事情就像:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder viewHolder;
if (convertView == null) {
LayoutInflater inflater = activity.getLayoutInflater();
convertView = inflater.inflate(R.layout.item, null, false);
viewHolder = new ViewHolder();
viewHolder.imageView = (ImageView) convertView.findViewById(R.id.image);
convertView.setTag(viewHolder);
} else {
viewHolder = (ViewHolder) convertView.getTag();
}
String url = components.get(position).getImageURL();
Glide.with(activity).load(url).crossFade().into(viewHolder.imageView);
return convertView;
}
虽然其他人是对的,有许多库可以为您处理此问题,但如果您确实愿意,也可以按照您最初尝试的方式进行处理。您只需要确保在项目视图被回收时取消 AsyncTasks。您可以通过将 AsyncTask 添加到您的 ViewHolder class 来完成此操作,这样您就可以查看是否有内容 运行 并在开始新的之前取消它。
private class ViewHolder {
ImageView imageView;
AsyncTask<?,?,?> task;
}
然后在你的 getView() 中:
@Override
public View getView(int position, View convertView, ViewGroup parent) {
...
// Don't show any image before the correct one is downloaded
viewHolder.imageView.setImageBitmap(null);
if (viewHolder.task != null) { // Existing task may be executing
viewHolder.task.cancel(true);
viewHolder.task = null;
}
if (component.getImage() == null) { // Image not downloaded already
AsyncTask<?,?,?> task = new DownloadImageTask(viewHolder.imageView);
viewHolder.task = task;
task.execute(component);
} else {
viewHolder.imageView.setImageBitmap(component.getImage());
}
return convertView;
}