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

Android应用隐藏空ViewPager2触发IllegalStateException问题排查

ViewPager2 隐藏后跳转页面崩溃问题分析

问题背景

首页存在一个ViewPager2,初始状态为空。通过API接口根据用户位置返回的数据,决定需要加载的Fragment数量,之后创建Fragment并关联TabLayout。当API异常返回空数据(即nbTab=0)时,会隐藏ViewPager2并显示提示布局,此时跳转到其他页面会触发崩溃。

核心实现代码

@AndroidEntryPoint
class ContentHomeFragment :
    LocationFragment<FragmentContentHomeBinding, ContentHomeViewModel>() {

    private lateinit var qaInformationAdapter: QaInformationAdapter

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        qaInformationAdapter = QaInformationAdapter(childFragmentManager, lifecycle)

        binding.viewPagerContent.adapter = qaInformationAdapter
        binding.tabDays.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {
            override fun onTabSelected(tab: TabLayout.Tab) {
                // ... 处理Tab选中逻辑,设置当前日期
                viewModel.setCurrentDay(day)
            }

            override fun onTabUnselected(tab: TabLayout.Tab) {
                // No op
            }

            override fun onTabReselected(tab: TabLayout.Tab) {
                // No op
            }
        })

        binding.btnToGoOut.setOnClickListener {
            // ... 跳转到其他页面
        }

        viewModel.apiInformation.observe(viewLifecycleOwner) { apiInfo ->
            displayExistingTab(apiInfo)
        }
    }

    private fun displayExistingTab(infoFromApi: List<InfoFromApi>) {
        tabTitle.clear()
        // ... 处理API数据,计算nbTab数量和内容
        if (nbTab == 0) {
            binding.viewPagerContent.visibility = View.GONE
            binding.layoutUnknownInfo.visibility = View.VISIBLE
        } else {
            binding.viewPagerContent.visibility = View.VISIBLE
            binding.layoutUnknownInfo.visibility = View.GONE
        }

        tabLayoutMediator?.detach()
        tabLayoutMediator =
            TabLayoutMediator(binding.tabDays, binding.viewPagerContent) { tab, position ->
                setTabInfo(tab, position, infoFromApi)
            }
        tabLayoutMediator?.attach()
        if (displayTodayTab) {
            displayTodayTab = false
            binding.tabDays.selectTab(binding.tabDays.getTabAt(todayTab))
        }

        qaInformationAdapter.nbItem = nbTab
    }

    private fun setTabInfo(
        tab: TabLayout.Tab,
        position: Int,
        infos: List<InfoFromApi>
    ) {
        val tabView = ItemTabHomeBinding.inflate(layoutInflater)
        // ... 设置Tab的标题、颜色等内容
    }
}

class QaInformationAdapter(
    fragmentManager: FragmentManager,
    lifeCycle: Lifecycle
) :
    FragmentStateAdapter(fragmentManager, lifeCycle) {

    companion object {
        private const val NUM_ITEM_MAX = 4
    }

    var nbItem = NUM_ITEM_MAX
        @SuppressLint("NotifyDataSetChanged")
        set(value) {
            field = value
            notifyDataSetChanged()
        }

    override fun getItemCount() = nbItem

    override fun createFragment(position: Int) =
        QaInformationFragment.newInstance(Day.values()[position])
}

崩溃场景与错误栈

仅当API异常导致nbTab=0,隐藏ViewPager2并显示提示布局后,跳转到其他页面时崩溃,错误栈如下:

java.lang.IllegalStateException: ViewPager2 does not support direct child views
        at androidx.viewpager2.widget.ViewPager2.onViewAdded(ViewPager2.java:489)
        at android.view.ViewGroup.dispatchViewAdded(ViewGroup.java:5129)
        at android.view.ViewGroup.addViewInner(ViewGroup.java:5301)
        at android.view.ViewGroup.addView(ViewGroup.java:5076)
        at android.view.ViewGroup.addView(ViewGroup.java:5016)
        at androidx.transition.TransitionUtils.createViewBitmap(TransitionUtils.java:138)
        at androidx.transition.TransitionUtils.copyViewImage(TransitionUtils.java:64)
        at androidx.transition.Visibility.onDisappear(Visibility.java:404)
        at androidx.transition.Visibility.createAnimator(Visibility.java:257)
        at androidx.transition.Transition.createAnimators(Transition.java:744)
        at androidx.transition.Transition.playTransition(Transition.java:1808)
        at androidx.transition.TransitionManager$MultiListener.onPreDraw(TransitionManager.java:300)
        at android.view.ViewTreeObserver.dispatchOnPreDraw(ViewTreeObserver.java:1093)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3300)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2126)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8653)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1037)
        at android.view.Choreographer.doCallbacks(Choreographer.java:845)
        at android.view.Choreographer.doFrame(Choreographer.java:780)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1022)
        at android.os.Handler.handleCallback(Handler.java:938)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loopOnce(Looper.java:201)
        at android.os.Looper.loop(Looper.java:288)
        at android.app.ActivityThread.main(ActivityThread.java:7839)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:548)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1003)

问题根源

这个崩溃的核心原因是页面跳转时的过渡动画逻辑与ViewPager2的内部机制冲突:

  1. 当ViewPager2被设置为View.GONE后,在页面跳转的Visibility过渡动画过程中,系统会调用TransitionUtils.createViewBitmap方法尝试生成这个GONE视图的快照,用于过渡动画的视觉效果。
  2. 在生成快照的过程中,TransitionUtils会创建一个临时的View容器,并尝试将目标View的内容复制到这个容器中。但当ViewPager2没有加载任何Fragment(nbTab=0)时,过渡逻辑错误地直接向ViewPager2本身添加了子View。
  3. 而ViewPager2的设计本身禁止直接添加子View(它内部只允许自己的RecyclerView作为子View),所以在onViewAdded方法中直接抛出了IllegalStateException。

解决方案建议

  • 方案1:包裹ViewPager2,隐藏父容器:不要直接设置ViewPager2的可见性,而是给它套一个父Layout(比如FrameLayout),通过隐藏父容器来达到隐藏ViewPager2的效果,避免过渡动画直接操作ViewPager2。
  • 方案2:跳转时禁用过渡动画:在跳转到其他页面的代码中,禁用Fragment/Activity的过渡动画,比如使用navOptions { anim { enter = 0; exit = 0; popEnter = 0; popExit = 0 } }(如果用Navigation组件)。
  • 方案3:清空适配器状态:在设置ViewPager2为GONE之前,先调用qaInformationAdapter.nbItem = 0并确保适配器完成更新,或者暂时移除适配器,避免过渡逻辑处理空状态的ViewPager2。
  • 方案4:使用提示布局覆盖:就是你已经想到的方式,保持ViewPager2可见,用提示布局覆盖在它上面,避免触发ViewPager2的GONE状态过渡逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 14:24:56