将带Fragment的ViewPager2迁移至HorizontalPager时滑动崩溃问题
问题:HorizontalPager滑动时触发"The specified child already has a parent"异常
我正逐步将基于Fragment的ViewPager2迁移为Compose的HorizontalPager,但滑动HorizontalPager时触发java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first异常。尝试过移除Fragment视图(如调用前一个Fragment的onDestroyView、从父容器移除视图等操作),但均未解决问题。
问题代码
@OptIn(ExperimentalFoundationApi::class) @Composable private fun ViewPager( data: List<Data>, fragmentManager: FragmentManager, fragmentContainerView: FragmentContainerView, currentItem: Int ) { val pagerState = rememberPagerState( initialPage = currentItem, pageCount = { data.size }) HorizontalPager(state = pagerState, userScrollEnabled = true) { page -> //if userScrollEnabled is set to false fragment does not crash but scroll not works val fragment = AboutAppFragment() AndroidView( modifier = Modifier.fillMaxSize(), factory = { val currentFragment = fragmentManager.findFragmentByTag("TEST_TAG") val transaction = fragmentManager.beginTransaction() currentFragment?.onDestroyView() currentFragment?.let(transaction::remove) transaction.commit() val transaction2 = fragmentManager.beginTransaction() transaction2.replace(fragmentContainerView.id, fragment, "TEST_TAG") transaction2.commit() fragmentContainerView } ) } } private fun getFragmentContainer() = FragmentContainerView(context = this@Activity).apply { id = View.generateViewId() }
完整堆栈信息
java.lang.IllegalStateException: The specified child already has a parent. You must call removeView() on the child's parent first. at android.view.ViewGroup.addViewInner(ViewGroup.java:5256) at android.view.ViewGroup.addView(ViewGroup.java:5085) at android.view.ViewGroup.addView(ViewGroup.java:5025) at android.view.ViewGroup.addView(ViewGroup.java:4997) at androidx.compose.ui.viewinterop.AndroidViewHolder.<init>(AndroidViewHolder.android.kt:98) at androidx.compose.ui.viewinterop.ViewFactoryHolder.<init>(AndroidView.android.kt:331) at androidx.compose.ui.viewinterop.ViewFactoryHolder.<init>(AndroidView.android.kt:322) at androidx.compose.ui.viewinterop.ViewFactoryHolder.<init>(AndroidView.android.kt:341) at androidx.compose.ui.viewinterop.AndroidView_androidKt$createAndroidViewNodeFactory$1.invoke(AndroidView.android.kt:274) at androidx.compose.ui.viewinterop.AndroidView_androidKt$createAndroidViewNodeFactory$1.invoke(AndroidView.android.kt:273) at androidx.compose.ui.viewinterop.AndroidView_androidKt$AndroidView$$inlined$ComposeNode$1.invoke(Composables.kt:254) at androidx.compose.runtime.changelist.Operation$InsertNodeFixup.execute(Operation.kt:476) at androidx.compose.runtime.changelist.Operations.executeAndFlushAllPendingOperations(Operations.kt:307) at androidx.compose.runtime.changelist.FixupList.executeAndFlushAllPendingFixups(FixupList.kt:50) at androidx.compose.runtime.changelist.Operation$InsertSlotsWithFixups.execute(Operation.kt:443) at androidx.compose.runtime.changelist.Operations.executeAndFlushAllPendingOperations(Operations.kt:307) at androidx.compose.runtime.changelist.ChangeList.executeAndFlushAllPendingChanges(ChangeList.kt:78) at androidx.compose.runtime.CompositionImpl.applyChangesInLocked(Composition.kt:976) at androidx.compose.runtime.CompositionImpl.applyChanges(Composition.kt:1005) at androidx.compose.runtime.Recomposer.composeInitial$runtime_release(Recomposer.kt:1099) at androidx.compose.runtime.ComposerImpl$CompositionContextImpl.composeInitial$runtime_release(Composer.kt:3599) at androidx.compose.runtime.CompositionImpl.composeInitial(Composition.kt:633) at androidx.compose.runtime.CompositionImpl.setContent(Composition.kt:619) at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcomposeInto(SubcomposeLayout.kt:500) at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:472) at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:463) at androidx.compose.ui.layout.LayoutNodeSubcompositionsState.subcompose(SubcomposeLayout.kt:447) at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$Scope.subcompose(SubcomposeLayout.kt:872) at androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScopeImpl.measure-0kLqBqw(LazyLayoutMeasureScope.kt:125) at androidx.compose.foundation.pager.PagerMeasureKt.getAndMeasure-SGf7dI0(PagerMeasure.kt:522) at androidx.compose.foundation.pager.PagerMeasureKt.measurePager-_JDW0YA(PagerMeasure.kt:203) at androidx.compose.foundation.pager.PagerMeasurePolicyKt$rememberPagerMeasurePolicy$1$1.invoke-0kLqBqw(PagerMeasurePolicy.kt:159) at androidx.compose.foundation.pager.PagerMeasurePolicyKt$rememberPagerMeasurePolicy$1$1.invoke(PagerMeasurePolicy.kt:69) at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$3$2$1.invoke-0kLqBqw(LazyLayout.kt:107) at androidx.compose.foundation.lazy.layout.LazyLayoutKt$LazyLayout$3$2$1.invoke(LazyLayout.kt:100) at androidx.compose.ui.layout.LayoutNodeSubcompositionsState$createMeasurePolicy$1.measure-3p2s80s(SubcomposeLayout.kt:709) at androidx.compose.ui.node.InnerNodeCoordinator.measure-BRTryo0(InnerNodeCoordinator.kt:126) at androidx.compose.foundation.AndroidOverscroll_androidKt$StretchOverscrollNonClippingLayer$2.invoke-3p2s80s(AndroidOverscroll.android.kt:584) at androidx.compose.foundation.AndroidOverscroll_androidKt$StretchOverscrollNonClippingLayer$2.invoke(AndroidOverscroll.android.kt:583) at androidx.compose.ui.layout.LayoutModifierImpl.measure-3p2s80s(LayoutModifier.kt:294) at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116) at androidx.compose.foundation.AndroidOverscroll_androidKt$StretchOverscrollNonClippingLayer$1.invoke-3p2s80s(AndroidOverscroll.android.kt:568) at androidx.compose.foundation.AndroidOverscroll_androidKt$StretchOverscrollNonClippingLayer$1.invoke(AndroidOverscroll.android.kt:567) at androidx.compose.ui.layout.LayoutModifierImpl.measure-3p2s80s(LayoutModifier.kt:294) at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116) at androidx.compose.ui.graphics.SimpleGraphicsLayerModifier.measure-3p2s80s(GraphicsLayerModifier.kt:646) at androidx.compose.ui.node.LayoutModifierNodeCoordinator.measure-BRTryo0(LayoutModifierNodeCoordinator.kt:116) at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:252) at androidx.compose.ui.node.LayoutNodeLayoutDelegate$performMeasureBlock$1.invoke(LayoutNodeLayoutDelegate.kt:251) at androidx.compose.runtime.snapshots.Snapshot$Companion.observe(Snapshot.kt:2303) at androidx.compose.runtime.snapshots.SnapshotStateObserver$ObservedScopeMap.observe(SnapshotStateObserver.kt:500) at androidx.compose.runtime.snapshots.SnapshotStateObserver.observeReads(SnapshotStateObserver.kt:256) at androidx.compose.ui.node.OwnerSnapshotObserver.observeReads$ui_release(OwnerSnapshotObserver.kt:133) at androidx.compose.ui.node.OwnerSnapshotObserver.observeMeasureSnapshotReads$ui_release(OwnerSnapshotObserver.kt:113) at androidx.compose.ui.node.LayoutNodeLayoutDelegate.performMeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:1617) at androidx.compose.ui.node.LayoutNodeLayoutDelegate$MeasurePassDelegate.remeasure-BRTryo0(LayoutNodeLayoutDelegate.kt:620) at androidx.compose.ui.node.LayoutNode.remeasure-_Sx5XlM$ui_release(LayoutNode.kt:1145) at androidx.compose.ui.node.MeasureAndLayoutDelegate.doRemeasure-sdFAvZA(MeasureAndLayoutDelegate.kt:354) at androidx.compose.ui.node.MeasureAndLayoutDelegate.measureAndLayout-0kLqBqw(MeasureAndLayoutDelegate.kt:439) at androidx.compose.ui.platform.AndroidComposeView.measureAndLayout-0kLqBqw(AndroidComposeView.android.kt:982) at androidx.compose.ui.node.LayoutNode.forceRemeasure(LayoutNode.kt:1219) at androidx.compose.foundation.pager.PagerState.performScroll(PagerState.kt:222) at androidx.compose.foundation.pager.PagerState.access$performScroll(PagerState.kt:131) at androidx.compose.foundation.pager.PagerState$scrollableState$1.invoke(PagerState.kt:184) at androidx.compose.foundation.pager.PagerState$scrollableState$1.invoke(PagerState.kt:184) at androidx.compose.foundation.gestures.DefaultScrollableState$scrollScope$1.scrollBy(ScrollableState.kt:166) at androidx.compose.foundation.gestures.ScrollingLogic$dispatchScroll$performScroll$1.invoke-MK-Hz9U(Scrollable.kt:762) at androidx.compose.foundation.gestures.ScrollingLogic$dispatchScroll$performScroll$1.invoke(Scrollable.kt:752) at androidx.compose.foundation.AndroidEdgeEffectOverscrollEffect.applyToScroll-Rhakbz0(AndroidOverscroll.android.kt:184) at androidx.compose.foundation.gestures.ScrollingLogic.dispatchScroll-3eAAhYA(Scrollable.kt:778) at androidx.compose.foundation.gestures.ScrollDraggableState.dragBy(Scrollable.kt:886) at androidx.compose.foundation.gestures.DraggableNode$abstractDragScope$1.dragBy-k-4lQ0M(Draggable.kt:315) at androidx.compose.foundation.gestures.DraggableNode.draggingBy(Draggable.kt:327) at androidx.compose.foundation.gestures.AbstractDraggableNode$startListeningForEvents$1$1.invoke(Unknown Source:8) at androidx.compose.foundation.gestures.AbstractDraggableNode$startListeningForEvents$1$1.invoke(Unknown Source:4) at androidx.compose.foundation.gestures.DraggableNode$drag$2.invokeSuspend(Draggable.kt:322) at androidx.compose.foundation.gestures.DraggableNode$drag$2.invoke(Unknown Source:8) at androidx.compose.foundation.gestures.DraggableNode$drag$2.invoke(Unknown Source:4) at androidx.compose.foundation.gestures.ScrollDraggableState$drag$2.invokeSuspend(Scrollable.kt:894) at androidx.compose.foundation.gestures.ScrollDraggableState$drag$2.invoke(Unknown Source:8) at androidx.compose.foundation.gestures.ScrollDraggableState$drag$2.invoke(Unknown Source:4) at androidx.compose.foundation.gestures.DefaultScrollableState$scroll$2$1.invokeSuspend(ScrollableState.kt:181) at androidx.compose.foundation.gestures.DefaultScrollableState$scroll$2$1.invoke(Unknown Source:8) at androidx.compose.foundation.gestures.DefaultScrollableState$scroll$2$1.invoke(Unknown Source:4) at androidx.compose.foundation.MutatorMutex$mutateWith$2.invokeSuspend(MutatorMutex.kt:173) at androidx.compose.foundation.MutatorMutex$mutateWith$2.invoke(Unknown Source:8) at androidx.compose.foundation.MutatorMutex$mutateWith$2.invoke(Unknown Source:4) at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:61) at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:261) at androidx.compose.foundation.MutatorMutex.mutateWith(MutatorMutex.kt:166) at androidx.compose.foundation.gestures.DefaultScrollableState$scroll$2.invokeSuspend(ScrollableState.kt:178) at androidx.compose.foundation.gestures.DefaultScrollableState$scroll$2.invoke(Unknown Source:8) at androidx.compose.foundation.gestures.DefaultScrollableState$scroll$2.invoke(Unknown Source:4) at kotlinx.coroutines.intrinsics.UndispatchedKt.startUndispatchedOrReturn(Undispatched.kt:61) at kotlinx.coroutines.CoroutineScopeKt.coroutineScope(CoroutineScope.kt:261) at androidx.compose.foundation.gestures.DefaultScrollableState.scroll(ScrollableState.kt:177) at androidx.compose.foundation.pager.PagerState.scroll$suspendImpl(PagerState.kt:575) at androidx.compose.foundation.pager.PagerState.scroll(Unknown Source:0) at androidx.compose.foundation.gestures.ScrollDraggableState.drag(Scrollable.kt:892) at androidx.compose.foundation.gestures.DraggableNode.drag(Draggable.kt:320) at androidx.compose.foundation.gestures.AbstractDraggableNode$startListeningForEvents$1.invokeSuspend(Draggable.kt:435) at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33) at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:104) at androidx.compose.ui.platform.AndroidUiDispatcher.performTrampolineDispatch(AndroidUiDispatcher.android.kt:81) at androidx.compose.ui.platform.AndroidUiDispatcher.access$performTrampolineDispatch(AndroidUiDispatcher.android.kt:41) at androidx.compose.ui.platform.AndroidUiDispatcher$dispatchCallback$1.doFrame(AndroidUiDispatcher.android.kt:68) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1404) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1415) at android.view.Choreographer.doCallbacks(Choreographer.java:1015) at android.view.Choreographer.doFrame(Choreographer.java:941) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1389) at android.os.Handler.handleCallback(Handler.java:959) at android.os.Handler.dispatchMessage(Handler.java:100) at android.os.Looper.loopOnce(Looper.java:232) at android.os.Looper.loop(Looper.java:317) at android.app.ActivityThread.main(ActivityThread.java:8705) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:886)
解决方案
问题核心
- 复用了同一个
FragmentContainerView实例在HorizontalPager的多个页面中,滑动时Compose尝试将已有父布局的View重复添加,触发异常。 - Fragment事务
commit()是异步执行的,连续调用会导致状态不一致;手动调用onDestroyView()不符合Fragment生命周期规范。
修复步骤
- 为每个Pager页面创建独立的FragmentContainerView,避免同一个View被多次添加到不同父布局。
- 使用
commitNow()替代commit(),确保Fragment事务立即执行,保证状态同步。 - 让FragmentManager管理生命周期,不要手动调用
onDestroyView()。
修改后的代码
@OptIn(ExperimentalFoundationApi::class) @Composable private fun ViewPager( data: List<Data>, fragmentManager: FragmentManager, currentItem: Int ) { val pagerState = rememberPagerState( initialPage = currentItem, pageCount = { data.size } ) HorizontalPager(state = pagerState, userScrollEnabled = true) { page -> AndroidView( modifier = Modifier.fillMaxSize(), factory = { context -> // 为每个页面创建独立的容器视图 val containerView
相关产品推荐
相关产品推荐

