You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

RecyclerView搭配Paging适配器运行异常 图片加载顺序错误求助

问题根因

你为了解决滚动错乱错误重写了getItemViewType返回当前position,这会导致RecyclerView的ViewHolder复用池完全失效:每个position对应唯一的viewType,RecyclerView无法复用已创建的ViewHolder,每新增一个可见项都要新建ViewHolder并触发图片请求。Picasso默认并发请求数有限,大量请求排队后就会出现滑动到后部项时需要等待前面所有图片加载完成的问题。

同时你的setHasStableIds配置方式也存在错误,不需要每次提交分页数据时重复调用,且没有配套重写getItemId方法。

解决方案

1. 删除错误的getItemViewType重写

所有列表项布局完全一致,不需要自定义viewType,直接删除你重写的返回position的getItemViewType方法即可,恢复RecyclerView的复用能力,大幅减少无效的图片请求。

2. 正确配置StableIds

开启StableIds只需要做两处修改:

  • 在Adapter初始化时一次调用setHasStableIds(true)
  • 重写getItemId方法返回Movie数据的唯一id,不要使用位置作为id

3. 优化图片加载逻辑

给Picasso添加滚动监听控制,滚动时暂停非可见区域的图片加载,优先加载当前屏幕可见项的图片,同时添加占位图避免空白。

4. 修复Paging适配问题

不要吞onBindViewHolder中的异常,对getItem返回的空值做安全处理,适配PagingData的占位符特性。

修改后完整代码

MovieAdapter.kt

class MovieAdapter : PagingDataAdapter<Movie,MovieAdapter.MovieViewHolder>(DiffUtilCallback()) {
    // 初始化时开启stableIds,仅需调用一次
    init {
        setHasStableIds(true)
    }

    // 重写getItemId返回Movie的唯一业务ID
    override fun getItemId(position: Int): Long {
        return getItem(position)?.id ?: RecyclerView.NO_ID
    }

    override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
        // 安全处理空数据,不吞异常
        getItem(position)?.let { movie ->
            holder.bind(movie)
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val view = inflater.inflate(R.layout.movie_list_item,parent,false)
        return MovieViewHolder(view)
    }

    // 已删除错误的getItemViewType重写

    class MovieViewHolder(view : View) : RecyclerView.ViewHolder(view){
        private val imgMoviePoster = view.findViewById<ImageView>(R.id.imgImagePoster)
        private val txtMovieTitle = view.findViewById<TextView>(R.id.txtMovieTitle)
        private val txtMovieReleaseDate = view.findViewById<TextView>(R.id.txtMovieReleaseDate)

        fun bind(movie : Movie){
            txtMovieTitle.text = movie.title
            txtMovieReleaseDate.text = movie.releaseDate
            val moviePosterUrl = POSTER_BASE_URL + movie.posterPath

            Picasso.get()
                .load(moviePosterUrl)
                .tag(itemView.context) // 设置和滚动监听匹配的tag
                .placeholder(R.drawable.placeholder) // 替换为你的占位图资源
                .error(R.drawable.load_error) // 替换为你的加载错误图资源
                .into(imgMoviePoster)
        }
    }

    class DiffUtilCallback : DiffUtil.ItemCallback<Movie>(){
        override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean {
            return newItem.id == oldItem.id
        }
        override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean {
            return newItem == oldItem
        }
    }
}

MainActivity.kt

private fun initRecyclerView(){
    recyclerViewPopMovie.apply {
        layoutManager = GridLayoutManager(this@MainActivity,2)
        movieAdapter = MovieAdapter()
        adapter = movieAdapter
        // 添加滚动监听控制Picasso加载优先级
        addOnScrollListener(object : RecyclerView.OnScrollListener() {
            override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                super.onScrollStateChanged(recyclerView, newState)
                if (newState == RecyclerView.SCROLL_STATE_IDLE) {
                    // 滚动停止时恢复加载
                    Picasso.get().resumeTag(this@MainActivity)
                } else {
                    // 滚动中暂停加载
                    Picasso.get().pauseTag(this@MainActivity)
                }
            }
        })
    }
}

private fun initViewModel(){
    lifecycleScope.launchWhenCreated {
        viewModel.getContent().collectLatest {
            // 删除重复的setHasStableIds调用
            movieAdapter.submitData(it)
        }
    }
}

修改完成后即可同时解决滚动错乱和图片排队加载的问题。

内容的提问来源于stack exchange,提问作者JProgrammer

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.01 15:06:05