Compose TabRow中Activity重启/配置变更时Fragment空白问题
Compose TabRow结合FragmentContainerView重启后Fragment无法显示
问题描述
在Compose的TabRow中使用FragmentContainerView展示现有Fragment,首次加载时一切正常,但Activity重启后,Fragment虽存在于FragmentManager中却无法显示。受架构限制,只能使用add方式添加Fragment,不能用replace。Fragment A、B、C仅包含一个显示自身名称的TextView。
问题代码
Activity.kt
@Composable fun MainScreen() { var selectedTabIndex by remember { mutableStateOf(0) } Column(Modifier.fillMaxSize()) { TabRow( selectedTabIndex = selectedTabIndex, backgroundColor = Color.Gray, ) { tabs.forEachIndexed { index, title -> Tab( text = { Text( text = title, maxLines = 1, overflow = TextOverflow.Ellipsis ) }, selected = selectedTabIndex == index, onClick = { selectedTabIndex = index }, selectedContentColor = Color.Black, unselectedContentColor = Color.White ) } } FragmentContainer( modifier = Modifier.fillMaxSize(), commit = getCommitFunction( index = selectedTabIndex ) ) } } private fun getFragment(index: Int): Fragment { return when (index) { 0 -> FragmentA.newInstance() 1 -> FragmentB.newInstance() else -> FragmentC.newInstance() } } private fun getCommitFunction( index: Int, ): FragmentTransaction.(containerId: Int) -> Unit = { containerId -> showPage(this, index, containerId) } private fun showPage(transaction: FragmentTransaction, position: Int, containerId: Int) { // hide current showing fragment tabs.forEach { fragmentTag -> val fragment: Fragment? = supportFragmentManager.findFragmentByTag(fragmentTag) if (fragment != null && fragment.isVisible) { transaction.hide(fragment) } } val fragmentTag = tabs[position] var fragment: Fragment? = supportFragmentManager.findFragmentByTag(fragmentTag) if (fragment == null) { // we don't have the fragment yet, need to create it fragment = getFragment(position) println("containerId $containerId") transaction.add(containerId, fragment, fragmentTag) } else { println("containerId else $containerId") // we already created the fragment, just need to show it transaction.show(fragment) } transaction.setPrimaryNavigationFragment(fragment) }
FragmentContainer.kt
@Composable fun FragmentContainer( modifier: Modifier = Modifier, commit: FragmentTransaction.(containerId: Int) -> Unit ) { val localView = LocalView.current // Find the parent fragment, if one exists. This will let us ensure that // fragments inflated via a FragmentContainerView are properly nested // (which, in turn, allows the fragments to properly save/restore their state) val parentFragment = remember(localView) { try { localView.findFragment<Fragment>() } catch (e: IllegalStateException) { // findFragment throws if no parent fragment is found null } } val containerId by rememberSaveable { mutableStateOf(View.generateViewId()) } val container = remember { mutableStateOf<FragmentContainerView?>(null) } val viewBlock: (Context) -> View = remember(localView) { { context -> FragmentContainerView(context).apply { id = containerId } } } AndroidView( modifier = modifier, factory = viewBlock, update = {view -> val fragmentManager = parentFragment?.childFragmentManager ?: (view.context as? FragmentActivity)?.supportFragmentManager fragmentManager?.commit { commit(view.id) } container.value = view as FragmentContainerView } ) // Set up a DisposableEffect that will clean up fragments when the FragmentContainer is disposed val localContext = LocalContext.current DisposableEffect(localView, localContext, container) { onDispose { val fragmentManager = parentFragment?.childFragmentManager ?: (localContext as? FragmentActivity)?.supportFragmentManager // Now find the fragment inflated via the FragmentContainerView val existingFragment = fragmentManager?.findFragmentById(container.value?.id ?: 0) if (existingFragment != null && !fragmentManager.isStateSaved) { // If the state isn't saved, that means that some state change // has removed this Composable from the hierarchy fragmentManager.commit(true) { remove(existingFragment) } } } } }
问题根源
- Activity重启后,FragmentManager自动恢复之前的Fragment实例,但这些Fragment的旧宿主容器已销毁,新创建的FragmentContainerView虽ID相同,Fragment却未重新关联到新容器,导致无法显示。
- 原代码仅隐藏可见的Fragment,重启后Fragment的
isVisible状态异常,导致部分Fragment未被正确隐藏,覆盖目标Fragment。 FragmentContainer的update块在Compose重组时重复执行commit,引发Fragment状态混乱。
修复方案
方案一:修正Fragment显示/隐藏逻辑
修改showPage方法,确保所有已添加的Fragment都被隐藏,同时检查已存在的Fragment是否关联到当前容器,异常时重新关联:
private fun showPage(transaction: FragmentTransaction, position: Int, containerId: Int) { // 隐藏所有已添加的Fragment,避免重启后状态异常 tabs.forEach { fragmentTag -> supportFragmentManager.findFragmentByTag(fragmentTag)?.let { if (it.isAdded) { transaction.hide(it) } } } val fragmentTag = tabs[position] var fragment: Fragment? = supportFragmentManager.findFragmentByTag(fragmentTag) if (fragment == null) { fragment = getFragment(position) transaction.add(containerId, fragment, fragmentTag) } else { // 检查Fragment是否关联到当前容器,异常时重新关联 if (fragment.containerId != containerId) { transaction.remove(fragment) transaction.add(containerId, fragment, fragmentTag) } transaction.show(fragment) } transaction.setPrimaryNavigationFragment(fragment) }
方案二:简化FragmentContainer逻辑,直接管理Fragment切换
将Fragment的初始化和切换逻辑从FragmentContainer中移出,直接在Tab点击事件中操作FragmentManager,避免Compose重组带来的重复commit:
修改后的Activity.kt
@Composable fun MainScreen() { var selectedTabIndex by rememberSaveable { mutableStateOf(0) } val fragmentManager = (LocalContext.current as FragmentActivity).supportFragmentManager val containerId by rememberSaveable { mutableStateOf(View.generateViewId()) } // 初始化Fragment,仅执行一次 LaunchedEffect(Unit) { tabs.forEachIndexed { index, tag -> if (fragmentManager.findFragmentByTag(tag) == null) { fragmentManager.commit { add(containerId, getFragment(index), tag) // 默认隐藏非初始Fragment if (index != selectedTabIndex) hide(fragmentManager.findFragmentByTag(tag)!!) } } } // 设置初始显示的Fragment fragmentManager.commit { show(fragmentManager.findFragmentByTag(tabs[selectedTabIndex])!!) setPrimaryNavigationFragment(fragmentManager.findFragmentByTag(tabs[selectedTabIndex])!!) } } Column(Modifier.fillMaxSize()) { TabRow( selectedTabIndex = selectedTabIndex, backgroundColor = Color.Gray, ) { tabs.forEachIndexed { index, title -> Tab( text = { Text( text = title, maxLines = 1, overflow = TextOverflow.Ellipsis ) }, selected = selectedTabIndex == index, onClick = { if (selectedTabIndex != index) { fragmentManager.commit { // 隐藏当前显示的Fragment hide(fragmentManager.findFragmentByTag(tabs[selectedTabIndex])!!) // 显示目标Fragment show(fragmentManager.findFragmentByTag(tabs[index])!!) setPrimaryNavigationFragment(fragmentManager.findFragmentByTag(tabs[index])!!) } selectedTabIndex = index } }, selectedContentColor = Color.Black, unselectedContentColor = Color.White ) } } // 简化后的FragmentContainer仅负责创建容器 FragmentContainer( modifier = Modifier.fillMaxSize(), containerId = containerId ) } } private fun getFragment(index: Int): Fragment { return when (index) { 0 -> FragmentA.newInstance() 1 -> FragmentB.newInstance() else -> FragmentC.newInstance() } }
修改后的FragmentContainer.kt
@Composable fun FragmentContainer( modifier: Modifier = Modifier, containerId: Int ) { AndroidView( modifier = modifier, factory = { context -> FragmentContainerView(context).apply { id = containerId } } ) }
该方案将Fragment管理逻辑从Compose重组中完全剥离,仅在初始化时创建Fragment,后续切换直接操作FragmentManager,彻底避免了重启后的状态异常问题。
内容的提问来源于stack exchange,提问作者coolswap11
相关产品推荐
相关产品推荐

