如何在 RecyclerView 中显示 "heavy" 张图片?

How to display "heavy" images in a 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 个屏幕,图像已正确加载。有时可以注意到进度条,它很快就消失了。

但是如果用户滚动速度非常快,图像会出现然后被替换,有时会替换两次:

一般来说,在快速滚动时很明显:

我的意图是:

如果能听到一些提示或解决此问题的方法,我将不胜感激。

这是我想出的解决方案。

停止闪烁

第一部分很简单:

引入了一个简单的索引:

int loadingPosition;

onBindViewHolder开头保存当前位置:

holder.loadingPosition = position;

在切换位图之前,会检查当前位置与上次启动位置的对比。如果已经启动了一个新任务,索引已经被预先更改,那么图像将不会被更新:

if (holder.loadingPosition == position) {
    holder.myImg.setImageBitmap(bitmap);

提高性能

为了防止任务在不需要时进一步 运行,引入了 Future 对象。它允许管理启动的任务,而 AsyncTask 不允许:

Future<?> future;

通过ExecutorService而不是AsyncTask启动异步任务:

holder.future = executor.submit(() -> { ...

正在停止 运行ning 任务,如果它仍然是 运行ning:

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);

也许同时使用这两种方法有点矫枉过正,但它提供了一定程度的保险。