RecyclerView滚动时显示错误电影图片,多次加载后才显示正确求助
哥们,你遇到的这个问题是RecyclerView开发里超常见的「ViewHolder复用导致图片加载混乱」的坑,核心原因很简单:RecyclerView会复用滚动出屏幕的ViewHolder来显示新条目,但如果之前ViewHolder绑定的图片加载任务还没完成,新条目绑定后,旧任务完成时会把旧图片设置到ImageView上,直到新的加载任务完成才会替换成正确的图片,就出现了你说的多次切换的情况。
下面给你几个靠谱的解决思路,按推荐程度排序:
1. 直接用成熟的图片加载库(最省心、最推荐)
自己手动写图片下载逻辑很容易踩复用的坑,Glide、Picasso、Coil这类主流图片加载库都已经内置处理了ViewHolder复用的问题——它们会自动取消当前ViewHolder上未完成的旧加载任务,同时支持内存/磁盘缓存、占位图、错误图等功能,代码还特别简洁。
比如用Glide的示例代码:
@Override public void onBindViewHolder(MovieViewHolder holder, int position) { Movie currentMovie = movieList.get(position); Glide.with(holder.itemView.getContext()) .load(currentMovie.getImageUrl()) .placeholder(R.drawable.default_movie_placeholder) // 加载过程中显示的占位图 .error(R.drawable.movie_load_error) // 加载失败时显示的图 .into(holder.movieImageView); }
用这类库基本能一键解决你的问题,还能省掉很多自己处理缓存、线程的麻烦。
2. 手动处理:重置ImageView+取消旧加载任务
如果必须自己实现图片下载逻辑,那要做两件事:
第一步:绑定新数据前重置ImageView
在onBindViewHolder的最开始,先把ImageView的内容清空或者设置为占位图,避免旧图片先显示出来:
@Override public void onBindViewHolder(MovieViewHolder holder, int position) { // 先重置ImageView,清除旧内容 holder.movieImageView.setImageResource(R.drawable.default_movie_placeholder); // 再绑定新数据、启动新的图片加载 Movie currentMovie = movieList.get(position); startImageDownload(currentMovie.getImageUrl(), holder.movieImageView); }
第二步:取消ViewHolder上未完成的旧加载任务
你需要在ViewHolder中保存当前的加载任务引用,当重新绑定时,先取消之前的任务,避免它完成后设置旧图片。比如用AsyncTask的示例:
// 自定义ViewHolder,保存当前加载任务 class MovieViewHolder extends RecyclerView.ViewHolder { ImageView movieImageView; AsyncTask<?, ?, ?> currentDownloadTask; public MovieViewHolder(@NonNull View itemView) { super(itemView); movieImageView = itemView.findViewById(R.id.iv_movie); } } @Override public void onBindViewHolder(MovieViewHolder holder, int position) { // 取消之前未完成的下载任务 if (holder.currentDownloadTask != null) { holder.currentDownloadTask.cancel(true); } // 重置ImageView holder.movieImageView.setImageResource(R.drawable.default_movie_placeholder); Movie currentMovie = movieList.get(position); // 启动新的下载任务,并保存引用到ViewHolder holder.currentDownloadTask = new AsyncTask<String, Void, Bitmap>() { @Override protected Bitmap doInBackground(String... urls) { // 下载图片前先判断任务是否被取消 if (isCancelled()) { return null; } return downloadBitmapFromUrl(urls[0]); // 你的图片下载逻辑 } @Override protected void onPostExecute(Bitmap bitmap) { // 任务没被取消且图片不为空时,才设置图片 if (!isCancelled() && bitmap != null) { holder.movieImageView.setImageBitmap(bitmap); } } }.execute(currentMovie.getImageUrl()); }
注意:在doInBackground和onPostExecute里都要判断任务是否被取消,避免无用操作。
总结
优先推荐用成熟的图片加载库,能帮你规避绝大多数图片加载相关的问题;如果必须自己实现,一定要记得重置ImageView和取消旧加载任务这两个关键点。
内容的提问来源于stack exchange,提问作者Aravind Pulagam

