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

ViewPager2 API数据拉取异常:首次打开Fragment无内容切换后恢复

问题排查与修复:MoviesFragment首次加载无内容、切换后ViewPager2正常的问题

问题现象:首次打开MoviesFragment时仅显示背景,无任何内容;切换至其他Fragment再返回后,ViewPager2及其他列表可正常工作。


问题根源分析

  1. ViewPager2初始化时机错误:在nowPlayingMovieList数据未加载完成时,就调用setViewPager()设置了当前页面位置,此时sliderAdapter.itemCount为0,导致页面定位逻辑失效,ViewPager2无法渲染内容。
  2. 协程未绑定生命周期:直接使用lifecycleScope.launch收集数据,未限制生命周期状态,可能导致数据收集时机与UI初始化不同步。
  3. 重复设置Adapter:在setViewPager()和viewpagerSlider.apply中重复给ViewPager2设置Adapter,造成状态混乱。
  4. 无限轮播逻辑缺陷:使用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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.02 20:44:54