如何在 RecyclerView 中显示“重”图像?
Posted
技术标签:
【中文标题】如何在 RecyclerView 中显示“重”图像?【英文标题】:How to display "heavy" images in a RecyclerView? 【发布时间】:2020-12-06 01:35:52 【问题描述】:我需要在 RecyclerView 中显示一些图像。 当用户滚动列表时,这些图像应该“即时”呈现。渲染每张图片需要很长时间:50-500ms。在图像显示给用户之前,会显示一个进度条。
由于渲染时间长,这部分被放置到 AsyncTask 中。
请看下面的代码:
class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>
class ViewHolder extends RecyclerView.ViewHolder
ImageView myImg;
ProgressBar myProgressBar;
public ViewHolder(View view)
super(itemView);
myImg = view.findViewById(R.id.myImg);
myProgressBar = view.findViewById(R.id.myProgressBar);
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position)
holder.myProgressBar.setVisibility(View.VISIBLE);
AsyncTask.execute(() ->
Bitmap bitmap = longRenderingFunction();
holder.myImg.post(() ->
holder.myImg.setImageBitmap(bitmap);
holder.myProgressBar.setVisibility(View.GONE);
如果用户正在缓慢滚动列表,例如每秒 1-2 个屏幕,图像加载正确。有时可以注意到进度条,它很快就消失了。
但如果用户滚动速度非常快,图像会出现然后被替换,有时会被替换两次:
总的来说,在快速滚动时很明显:
调用多个onBindViewHolder
,并启动每个可回收物品ViewHolder
多个AsyncTasks
。
即使是针对同一项目触发了新的onBindViewHolder
,旧的AsyncTask
也会继续运行
然后AsyncTasks
为同一个ViewHolder
一个接一个完成。
每个AsyncTask
将自己的生成位图放到ImageView
。
我的意图是:
最低:不要从过时的setImageBitmap
AsyncTasks
最大:尽快停止已经过时的AsyncTasks
以节省系统资源
我希望听到一些提示或解决此问题的方法。
【问题讨论】:
你在正确的轨道上。只需取消AsyncTask
。棘手的部分是在哪里保存对AsyncTask
的引用以让您取消它。我认为您应该在适配器中创建一个HashMap<int, AsyncTask>
来存储每个位置的每个任务。
虽然您可以自行管理,但我强烈建议您查看诸如 Glide 之类的库,这些库旨在为您在加载图像时管理大量繁重的工作。
@CarsonHolzheimer 取消任务是个好主意。在这里,我仍然缺少在 android 中执行此操作的正确方法。我的渲染器被打包成一个非常紧凑的可运行文件。 AsyncTask.execute
也是一个紧凑的结构。我想保持紧凑。如果我创建一个正确 AsyncTask
,那么它就会变成一个带有doInBackground
等的怪物。所以我在这里考虑ExecutorService
,它可以返回Future<>
,并且可以取消任何时间。你怎么看?
【参考方案1】:
这是我想出的解决方案。
停止闪烁
第一部分非常简单:
引入了一个简单的索引:
int loadingPosition;
onBindViewHolder
的开头保存当前位置:
holder.loadingPosition = position;
在切换位图之前,会根据上次启动的位置检查当前位置。如果一个新的任务已经开始了,索引是事先改变的,那么图片就不会更新:
if (holder.loadingPosition == position)
holder.myImg.setImageBitmap(bitmap);
提高性能
为了防止任务在不需要时进一步运行,引入了Future
对象。它允许管理已启动的任务,而AsyncTask
不允许:
Future<?> future;
通过ExecutorService
而不是AsyncTask
启动异步任务:
holder.future = executor.submit(() -> ...
停止正在运行的任务,如果它仍在运行:
if ((holder.future != null) && (!holder.future.isDone()))
holder.future.cancel(true);
完整代码
整个代码如下:
class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder>
private ExecutorService executor = Executors.newSingleThreadExecutor();
class ViewHolder extends RecyclerView.ViewHolder
ImageView myImg;
ProgressBar myProgressBar;
int loadingPosition;
Future<?> future;
public ViewHolder(View view)
super(itemView);
myImg = view.findViewById(R.id.myImg);
myProgressBar = view.findViewById(R.id.myProgressBar);
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position)
holder.loadingPosition = position;
if ((holder.future != null) && (!holder.future.isDone()))
holder.future.cancel(true);
holder.myProgressBar.setVisibility(View.VISIBLE);
holder.future = executor.submit(() ->
Bitmap bitmap = longRenderingFunction();
holder.myImg.post(() ->
if (holder.loadingPosition == position)
holder.myImg.setImageBitmap(bitmap);
holder.myProgressBar.setVisibility(View.GONE);
也许同时使用这两种方法有点矫枉过正,但它提供了一定程度的保险。
【讨论】:
以上是关于如何在 RecyclerView 中显示“重”图像?的主要内容,如果未能解决你的问题,请参考以下文章
如何在 Kotlin 中显示来自 RecyclerView 的全屏图像?
无法在 Recyclerview 中显示图像 - 文本已成功显示
Android:在 Recyclerview 中更改图像旋转