ViewPager2 API数据拉取异常:首次打开Fragment无内容切换后恢复
问题排查与修复:MoviesFragment首次加载无内容、切换后ViewPager2正常的问题
问题现象:首次打开MoviesFragment时仅显示背景,无任何内容;切换至其他Fragment再返回后,ViewPager2及其他列表可正常工作。
问题根源分析
- ViewPager2初始化时机错误:在
nowPlayingMovieList数据未加载完成时,就调用setViewPager()设置了当前页面位置,此时sliderAdapter.itemCount为0,导致页面定位逻辑失效,ViewPager2无法渲染内容。 - 协程未绑定生命周期:直接使用
lifecycleScope.launch收集数据,未限制生命周期状态,可能导致数据收集时机与UI初始化不同步。 - 重复设置Adapter:在
setViewPager()和viewpagerSlider.apply中重复给ViewPager2设置Adapter,造成状态混乱。 - 无限轮播逻辑缺陷:使用
Int.MAX_VALUE计算中间位置,在无数据时会引发无效计算,导致首次加载页面异常。
修复方案
1. 调整数据收集与ViewPager初始化顺序
确保数据加载完成后再初始化ViewPager的页面配置,同时给所有数据收集协程绑定生命周期:
// 替换原ViewPager相关代码 lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.nowPlayingMovieList.collect { pagingData -> sliderAdapter.submitData(pagingData) // 等待Adapter加载完成后初始化ViewPager lifecycleScope.launch { sliderAdapter.loadStateFlow.collectLatest { loadStates -> if (loadStates.refresh is LoadState.NotLoading && sliderAdapter.itemCount > 0) { setViewPager() } } } } } } sliderAdapter.setOnItemClickListener { val direction = MoviesFragmentDirections.actionMoviesFragmentToMovieDetailsFragment(it.id, it.poster_path) findNavController().navigate(direction) } // 先给ViewPager绑定Adapter binding.viewpagerSlider.adapter = sliderAdapter
同时给其他列表的协程也添加repeatOnLifecycle,比如热门电影的收集:
lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.popularMovieList.collect { popularMoviesAdapter.submitData(it) } } }
(TopRated、Upcoming列表的协程做同样修改)
2. 修正setViewPager()方法
移除重复设置Adapter的代码,修复无限轮播逻辑:
private fun setViewPager() { binding.viewpagerSlider.apply { clipToPadding = false clipChildren = false offscreenPageLimit = 3 getChildAt(0).overScrollMode = RecyclerView.OVER_SCROLL_NEVER } val itemCount = sliderAdapter.itemCount if (itemCount == 0) return // 无数据时直接返回,避免异常 // 基于实际数据量设置初始中间位置,替代Int.MAX_VALUE的不合理逻辑 val middlePosition = itemCount / 2 binding.viewpagerSlider.setCurrentItem(middlePosition, false) binding.viewpagerSlider.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { override fun onPageSelected(position: Int) { super.onPageSelected(position) // 修正无限轮播边界跳转逻辑,避免闪烁 when (position) { 0 -> binding.viewpagerSlider.setCurrentItem(itemCount - 2, false) itemCount - 1 -> binding.viewpagerSlider.setCurrentItem(1, false) } } }) val compositePageTransformer = CompositePageTransformer() compositePageTransformer.addTransformer(MarginPageTransformer(25)) compositePageTransformer.addTransformer { page, position -> val r: Float = 1 - abs(position) page.scaleY = 0.90f + r * 0.10f } binding.viewpagerSlider.setPageTransformer(compositePageTransformer) }
3. 移除冗余代码
删除viewpagerSlider.apply中重复设置Adapter的语句:
// 原代码中的这段冗余代码删除 viewpagerSlider.apply { setViewPager() adapter = sliderAdapter // 移除该行 }
核心修复点说明
- 生命周期绑定:
repeatOnLifecycle(Lifecycle.State.STARTED)确保协程仅在Fragment活跃时运行,避免后台资源浪费,同时保证数据收集与UI状态同步。 - 延迟初始化ViewPager:通过监听
loadStateFlow等待数据加载完成后再初始化页面配置,避免无数据时的无效计算。 - 合理的无限轮播逻辑:基于实际数据量设置初始位置,修正边界跳转逻辑,解决首次加载时的页面异常问题。
内容的提问来源于stack exchange,提问作者Berkay
相关产品推荐
相关产品推荐

