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

屏幕旋转时RecyclerView数据重复加载,列表重叠问题求助

问题:屏幕旋转时RecyclerView新旧列表叠加

在Android应用开发中,屏幕旋转时RecyclerView会重新加载数据,出现新列表叠加在旧列表之上的情况——滚动时可查看新数据,但背景中会显示旧列表。

相关代码片段

MoviesFragment

class MoviesFragment : Fragment() {

    private var viewModel: MoviesViewModel? = null
    private var adapter = MoviesAdapter()
    private lateinit var binding : FragmentMoviesBinding

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        binding = FragmentMoviesBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        recyclerViewInit()
        viewModelInit()
    }

    private fun recyclerViewInit() {
        val recyclerView = binding.recyclerView
        recyclerView.adapter = adapter
        recycler_view.layoutManager = LinearLayoutManager(requireContext())
    }

    private fun viewModelInit() {
        viewModel = ViewModelProvider(this)[MoviesViewModel::class.java]
        viewModel!!.getPageOfMovies()
        viewModel!!.myResponse.observe(viewLifecycleOwner) { response ->
            if (response.isSuccessful) {
                response.body()?.let { movies ->
                    adapter.setData(movies) }
            } else {
                Toast.makeText(requireContext(), response.code(), Toast.LENGTH_SHORT).show()
            }

        }
    }
}

MoviesViewModel

class MoviesViewModel(application: Application) : AndroidViewModel(application) {

    private val repository = MoviesRepository()
    val myResponse: MutableLiveData<Response<MovieResponse>> = MutableLiveData()

    fun getPageOfMovies() {
        viewModelScope.launch {
            val response = repository.getPageOfMovies()
            myResponse.value = response
        }
    }
}

MoviesAdapter

class MoviesAdapter : RecyclerView.Adapter<MoviesAdapter.ViewHolder>() {
    private var moviesList = MovieResponse(0, emptyList(), 0, 0)

    inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(
            LayoutInflater.from(parent.context).inflate(
                R.layout.movies_recyclerview_item, parent, false
            )
        )
    }

    override fun getItemCount(): Int = moviesList.results.size

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        Glide.with(holder.itemView.context)
            .load(Uri.parse("$BASE_URI_FOR_IMAGES${moviesList.results[position].posterPath}"))
            .centerCrop()
            .into(holder.itemView.poster_imageview)
        holder.itemView.title.text = moviesList.results[position].title
        holder.itemView.overview.text = moviesList.results[position].overview
        holder.itemView.release_date.text = moviesList.results[position].releaseDate
    }

    @SuppressLint("NotifyDataSetChanged")
    fun setData(movies: MovieResponse) {
        moviesList = movies
        notifyDataSetChanged()
    }

    companion object{
        const val BASE_URI_FOR_IMAGES = "https://www.themoviedb.org/t/p/w220_and_h330_face/"
    }
}

MoviesRepository

class MoviesRepository {
    suspend fun getPageOfMovies(): Response<MovieResponse> {
        return MoviesClient.api.getFirstPageOfPopularMovies()
    }
}

问题原因分析

  1. RecyclerView布局管理器设置错误:代码中recycler_view.layoutManager直接引用XML ID(可能通过Kotlin扩展),而非View Binding获取的当前Fragment视图实例,导致旧的RecyclerView被重复配置,新旧视图叠加显示。
  2. 重复发起网络请求:屏幕旋转时Fragment重建,每次都会调用viewModel!!.getPageOfMovies(),导致LiveData先后发送缓存的旧数据和新请求的结果,两次触发adapter更新,若item布局有透明属性,会出现视觉上的叠加效果。
  3. LiveData重复回调:屏幕旋转后新的Fragment观察者会立即收到LiveData缓存的旧数据,同时新的网络请求返回新数据,两次数据更新导致列表内容被重复渲染。

解决方法

1. 修正RecyclerView布局管理器配置

将recyclerViewInit()中的错误代码修正,确保使用当前Fragment的View Binding实例设置布局管理器:

private fun recyclerViewInit() {
    val recyclerView = binding.recyclerView
    recyclerView.adapter = adapter
    // 修正为使用binding获取的RecyclerView实例
    recyclerView.layoutManager = LinearLayoutManager(requireContext())
    // 可选:设置固定大小提升RecyclerView性能
    recyclerView.setHasFixedSize(true)
}

2. 避免重复发起网络请求

修改ViewModel或Fragment逻辑,仅在没有数据时发起请求:

  • ViewModel层面优化:
class MoviesViewModel(application: Application) : AndroidViewModel(application) {
    private val repository = MoviesRepository()
    val myResponse: MutableLiveData<Response<MovieResponse>> = MutableLiveData()

    fun getPageOfMovies() {
        // 仅当LiveData无数据时发起请求
        if (myResponse.value == null) {
            viewModelScope.launch {
                val response = repository.getPageOfMovies()
                myResponse.value = response
            }
        }
    }
}
  • Fragment层面优化:
private fun viewModelInit() {
    viewModel = ViewModelProvider(this)[MoviesViewModel::class.java]
    // 仅当LiveData无数据时触发请求
    if (viewModel?.myResponse?.value == null) {
        viewModel!!.getPageOfMovies()
    }
    viewModel!!.myResponse.observe(viewLifecycleOwner) { response ->
        if (response.isSuccessful) {
            response.body()?.let { movies ->
                adapter.setData(movies)
            }
        } else {
            Toast.makeText(requireContext(), response.code().toString(), Toast.LENGTH_SHORT).show()
        }
    }
}

3. 优化Adapter数据更新逻辑

使用更精确的通知方法替代notifyDataSetChanged(),确保数据完全替换:

fun setData(movies: MovieResponse) {
    val oldItemCount = moviesList.results.size
    moviesList = movies
    // 先通知移除旧列表所有项
    if (oldItemCount > 0) {
        notifyItemRangeRemoved(0, oldItemCount)
    }
    // 再通知添加新列表所有项
    val newItemCount = moviesList.results.size
    if (newItemCount > 0) {
        notifyItemRangeInserted(0, newItemCount)
    }
}

4. 清理Fragment视图引用

在onDestroyView()中清空RecyclerView的adapter引用,避免旧视图残留:

override fun onDestroyView() {
    super.onDestroyView()
    binding.recyclerView.adapter = null
    viewModel = null
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 23:10:53