屏幕旋转时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() } }
问题原因分析
- RecyclerView布局管理器设置错误:代码中
recycler_view.layoutManager直接引用XML ID(可能通过Kotlin扩展),而非View Binding获取的当前Fragment视图实例,导致旧的RecyclerView被重复配置,新旧视图叠加显示。 - 重复发起网络请求:屏幕旋转时Fragment重建,每次都会调用
viewModel!!.getPageOfMovies(),导致LiveData先后发送缓存的旧数据和新请求的结果,两次触发adapter更新,若item布局有透明属性,会出现视觉上的叠加效果。 - 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
相关产品推荐
相关产品推荐

