如何解决如何在RecyclerView中显示“大量”图像?
我需要在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
,并且每个可回收物料AsyncTasks
启动了多个ViewHolder
。 - 即使为同一项目触发了新的
onBindViewHolder
,旧的AsyncTask
仍在运行 - 然后
AsyncTasks
代表同一个ViewHolder
接连完成。 - 每个
AsyncTask
都将自己的结果位图放入ImageView
。
我的意图是:
- 最低限度:请勿从过时的
setImageBitmap
中AsyncTasks
- 最大值:尽快停止已经过时的
AsyncTasks
,以节省系统资源
我希望能听到一些提示或解决此问题的方法。
解决方法
这是我想出的解决方案。
停止闪烁
第一部分非常简单:
引入了一个简单的索引:
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);
也许同时使用这两种方法有些过分,但它提供了一定程度的保险。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。