在 RecyclerView 中滚动时图像闪烁
Image flickering while scrolling in RecyclerView
我正在尝试显示在直接从 MediaStore 请求数据的设备上找到的歌曲列表。我正在使用 RecyclerView
和一个使用 CursorAdapter
从 MediaStore 获取数据的适配器。
当适配器的 onBindViewHolder
被调用时,请求被传递到 CursorAdapter
的 bindView
函数,所有视觉元素都被设置。
public class ListRecyclerAdapter3 extends RecyclerView.Adapter<ListRecyclerAdapter3.SongViewHolder> {
// PATCH: Because RecyclerView.Adapter in its current form doesn't natively support
// cursors, we "wrap" a CursorAdapter that will do all teh job
// for us
public MediaStoreHelper mediaStoreHelper;
CustomCursorAdapter mCursorAdapter;
Context mContext;
public class SongViewHolder extends RecyclerView.ViewHolder {
public TextView textItemTitle;
public TextView textItemSub;
public ImageView imgArt;
public int position;
public String album_id;
public String path_art;
public String path_file;
public SongViewHolder(View v) {
super(v);
textItemTitle = (TextView) v.findViewById(R.id.textItemTitle);
textItemSub = (TextView) v.findViewById(R.id.textItemSub);
imgArt = (ImageView) v.findViewById(R.id.imgArt);
}
}
private class CustomCursorAdapter extends CursorAdapter {
public CustomCursorAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
}
@Override
public View newView(final Context context, Cursor cursor, ViewGroup parent) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.song_item, parent, false);
final SongViewHolder holder = new SongViewHolder(v);
v.setTag(holder);
return v;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
SongViewHolder holder = (SongViewHolder) view.getTag();
holder.position = cursor.getPosition();
holder.textItemTitle.setText(cursor.getString(cursor.getColumnIndex("title")));
holder.textItemSub.setText(cursor.getString(cursor.getColumnIndex("artist")));
holder.album_id = cursor.getString(cursor.getColumnIndex("album_id"));
holder.path_file = cursor.getString(cursor.getColumnIndex("_data"));
Picasso.with(holder.imgArt.getContext())
.cancelRequest(holder.imgArt);
holder.imgArt.setImageDrawable(null);
new DownloadImageTask(mediaStoreHelper, context, holder.imgArt).execute(holder.album_id);
}
}
private class DownloadImageTask extends AsyncTask<String, String, String> {
private MediaStoreHelper mediaStoreHelper;
private ImageView imageView;
private Context context;
public DownloadImageTask(MediaStoreHelper mediaStoreHelper, Context context, ImageView imageView)
{
this.mediaStoreHelper = mediaStoreHelper;
this.context = context;
this.imageView = imageView;
}
@Override
protected String doInBackground(String... ids) {
return mediaStoreHelper.getAlbumArtPath(ids[0]);
}
protected void onPostExecute(String result) {
Picasso.with(context)
.load(new File(result))
.placeholder(R.drawable.ic_music)
.fit()
.into(imageView);
}
}
@Override
public void onBindViewHolder(SongViewHolder holder, int position) {
// Passing the binding operation to cursor loader
mCursorAdapter.getCursor().moveToPosition(position);
mCursorAdapter.bindView(holder.itemView, mContext, mCursorAdapter.getCursor());
}
@Override
public SongViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// Passing the inflater job to the cursor-adapter
View v = mCursorAdapter.newView(mContext, mCursorAdapter.getCursor(), parent);
return new SongViewHolder(v);
}
}
有问题的部分是图像加载由两部分组成:
- 使用从
Cursor
获得的 albumId,我需要使用 ContentResolver
来获取专辑封面文件路径
- 使用文件路径
将图像加载到ImageView
这两个段落需要在后台完成,否则滚动会变得非常卡顿。在 bindView
函数中,我调用了 AsyncTask
来完成这项工作,但问题是,在快速滚动的同时,几个图像请求被详细说明,结果如下:
正如您从代码中看到的那样,我试图取消 Picasso 对特定 ImageView
的未决请求,但这还不够。这个问题可以解决吗?
评论这些行
发生闪烁是因为绑定不是只为单个项目调用一次,所以它一次又一次地为单行调用,并且您每次都设置 null 并在其上设置视图。产生闪烁。
Picasso.with(holder.imgArt.getContext())
.cancelRequest(holder.imgArt);
holder.imgArt.setImageDrawable(null);
您使用的异步任务完全错误。
首先你不应该执行匿名异步任务。
其次,摆脱适配器中的这个异步任务,因为它似乎是导致问题的原因。
获取适配器的数据,然后显示它。
顺便说一句,最好这样终止 picasso:
@Override public void onViewRecycled(VH holder) {
super.onViewRecycled(holder);
Picasso.with(holder.itemView.getContext())
.cancelRequest(holder.getImageView());
}
我通过在 ViewHolder
中添加一个包含与该项目相关的 AsyncTask
的字段来解决。在 bindView
函数中,我首先设置 AsyncTask.cancel(true)
,在任务中,我在使用 Picasso.with(...).load(...)
应用检索到的图像之前检查了 isCancelled()
。这本身解决了闪烁。
bindView
if(holder.downloadImageTask != null)
holder.downloadImageTask.cancel(true);
holder.downloadImageTask = (DownloadImageTask) new DownloadImageTask(mediaStoreHelper, context, holder.imgArt).execute(holder.album_id);
AsyncTask
private class DownloadImageTask extends AsyncTask<String, String, String> {
private MediaStoreHelper mediaStoreHelper;
private ImageView imageView;
private Context context;
public DownloadImageTask(MediaStoreHelper mediaStoreHelper, Context context, ImageView imageView)
{
this.mediaStoreHelper = mediaStoreHelper;
this.context = context;
this.imageView = imageView;
}
@Override
protected String doInBackground(String... ids) {
return mediaStoreHelper.getAlbumArtPath(ids[0]);
}
protected void onPostExecute(String result) {
if(!isCancelled())
Picasso.with(context)
.load(new File(result))
.placeholder(R.drawable.ic_music)
.fit()
.into(imageView);
}
}
为了完整起见,这还删除了回收物品中的图像并设置了一个占位符。
@Override
public void onViewRecycled(SongViewHolder holder) {
super.onViewRecycled(holder);
Picasso.with(holder.itemView.getContext())
.cancelRequest(holder.imgArt);
Picasso.with(holder.itemView.getContext())
.load(R.drawable.ic_music)
.fit()
.into(holder.imgArt);
}
这个解决方案让我认为问题是从 MediaStore 检索图像与图像实际应用到 Picasso 的 ImageView
之间的时间 AsyncTask
中发生的时间。
我正在尝试显示在直接从 MediaStore 请求数据的设备上找到的歌曲列表。我正在使用 RecyclerView
和一个使用 CursorAdapter
从 MediaStore 获取数据的适配器。
当适配器的 onBindViewHolder
被调用时,请求被传递到 CursorAdapter
的 bindView
函数,所有视觉元素都被设置。
public class ListRecyclerAdapter3 extends RecyclerView.Adapter<ListRecyclerAdapter3.SongViewHolder> {
// PATCH: Because RecyclerView.Adapter in its current form doesn't natively support
// cursors, we "wrap" a CursorAdapter that will do all teh job
// for us
public MediaStoreHelper mediaStoreHelper;
CustomCursorAdapter mCursorAdapter;
Context mContext;
public class SongViewHolder extends RecyclerView.ViewHolder {
public TextView textItemTitle;
public TextView textItemSub;
public ImageView imgArt;
public int position;
public String album_id;
public String path_art;
public String path_file;
public SongViewHolder(View v) {
super(v);
textItemTitle = (TextView) v.findViewById(R.id.textItemTitle);
textItemSub = (TextView) v.findViewById(R.id.textItemSub);
imgArt = (ImageView) v.findViewById(R.id.imgArt);
}
}
private class CustomCursorAdapter extends CursorAdapter {
public CustomCursorAdapter(Context context, Cursor c, int flags) {
super(context, c, flags);
}
@Override
public View newView(final Context context, Cursor cursor, ViewGroup parent) {
View v = LayoutInflater.from(parent.getContext())
.inflate(R.layout.song_item, parent, false);
final SongViewHolder holder = new SongViewHolder(v);
v.setTag(holder);
return v;
}
@Override
public void bindView(View view, Context context, Cursor cursor) {
SongViewHolder holder = (SongViewHolder) view.getTag();
holder.position = cursor.getPosition();
holder.textItemTitle.setText(cursor.getString(cursor.getColumnIndex("title")));
holder.textItemSub.setText(cursor.getString(cursor.getColumnIndex("artist")));
holder.album_id = cursor.getString(cursor.getColumnIndex("album_id"));
holder.path_file = cursor.getString(cursor.getColumnIndex("_data"));
Picasso.with(holder.imgArt.getContext())
.cancelRequest(holder.imgArt);
holder.imgArt.setImageDrawable(null);
new DownloadImageTask(mediaStoreHelper, context, holder.imgArt).execute(holder.album_id);
}
}
private class DownloadImageTask extends AsyncTask<String, String, String> {
private MediaStoreHelper mediaStoreHelper;
private ImageView imageView;
private Context context;
public DownloadImageTask(MediaStoreHelper mediaStoreHelper, Context context, ImageView imageView)
{
this.mediaStoreHelper = mediaStoreHelper;
this.context = context;
this.imageView = imageView;
}
@Override
protected String doInBackground(String... ids) {
return mediaStoreHelper.getAlbumArtPath(ids[0]);
}
protected void onPostExecute(String result) {
Picasso.with(context)
.load(new File(result))
.placeholder(R.drawable.ic_music)
.fit()
.into(imageView);
}
}
@Override
public void onBindViewHolder(SongViewHolder holder, int position) {
// Passing the binding operation to cursor loader
mCursorAdapter.getCursor().moveToPosition(position);
mCursorAdapter.bindView(holder.itemView, mContext, mCursorAdapter.getCursor());
}
@Override
public SongViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// Passing the inflater job to the cursor-adapter
View v = mCursorAdapter.newView(mContext, mCursorAdapter.getCursor(), parent);
return new SongViewHolder(v);
}
}
有问题的部分是图像加载由两部分组成:
- 使用从
Cursor
获得的 albumId,我需要使用ContentResolver
来获取专辑封面文件路径 - 使用文件路径 将图像加载到
ImageView
这两个段落需要在后台完成,否则滚动会变得非常卡顿。在 bindView
函数中,我调用了 AsyncTask
来完成这项工作,但问题是,在快速滚动的同时,几个图像请求被详细说明,结果如下:
正如您从代码中看到的那样,我试图取消 Picasso 对特定 ImageView
的未决请求,但这还不够。这个问题可以解决吗?
评论这些行
发生闪烁是因为绑定不是只为单个项目调用一次,所以它一次又一次地为单行调用,并且您每次都设置 null 并在其上设置视图。产生闪烁。
Picasso.with(holder.imgArt.getContext())
.cancelRequest(holder.imgArt);
holder.imgArt.setImageDrawable(null);
您使用的异步任务完全错误。
首先你不应该执行匿名异步任务。
其次,摆脱适配器中的这个异步任务,因为它似乎是导致问题的原因。
获取适配器的数据,然后显示它。
顺便说一句,最好这样终止 picasso:
@Override public void onViewRecycled(VH holder) {
super.onViewRecycled(holder);
Picasso.with(holder.itemView.getContext())
.cancelRequest(holder.getImageView());
}
我通过在 ViewHolder
中添加一个包含与该项目相关的 AsyncTask
的字段来解决。在 bindView
函数中,我首先设置 AsyncTask.cancel(true)
,在任务中,我在使用 Picasso.with(...).load(...)
应用检索到的图像之前检查了 isCancelled()
。这本身解决了闪烁。
bindView
if(holder.downloadImageTask != null)
holder.downloadImageTask.cancel(true);
holder.downloadImageTask = (DownloadImageTask) new DownloadImageTask(mediaStoreHelper, context, holder.imgArt).execute(holder.album_id);
AsyncTask
private class DownloadImageTask extends AsyncTask<String, String, String> {
private MediaStoreHelper mediaStoreHelper;
private ImageView imageView;
private Context context;
public DownloadImageTask(MediaStoreHelper mediaStoreHelper, Context context, ImageView imageView)
{
this.mediaStoreHelper = mediaStoreHelper;
this.context = context;
this.imageView = imageView;
}
@Override
protected String doInBackground(String... ids) {
return mediaStoreHelper.getAlbumArtPath(ids[0]);
}
protected void onPostExecute(String result) {
if(!isCancelled())
Picasso.with(context)
.load(new File(result))
.placeholder(R.drawable.ic_music)
.fit()
.into(imageView);
}
}
为了完整起见,这还删除了回收物品中的图像并设置了一个占位符。
@Override
public void onViewRecycled(SongViewHolder holder) {
super.onViewRecycled(holder);
Picasso.with(holder.itemView.getContext())
.cancelRequest(holder.imgArt);
Picasso.with(holder.itemView.getContext())
.load(R.drawable.ic_music)
.fit()
.into(holder.imgArt);
}
这个解决方案让我认为问题是从 MediaStore 检索图像与图像实际应用到 Picasso 的 ImageView
之间的时间 AsyncTask
中发生的时间。