如何防止点击穿透自定义Compose底部弹窗蒙层?
问题:自定义CustomSheet无法阻挡底层交互的解决思路
我希望自定义的CustomSheet能够完全覆盖屏幕,并阻挡底层屏幕的所有交互。原本以为处于上层的Composable组件默认会具备该行为,但目前即使CustomSheet已完全覆盖屏幕,我仍能点击HomeScreen上的按钮。请问有解决思路吗?
代码详情
CustomSheet包装器
val visibleState = remember { MutableTransitionState(false) }.apply { targetState = visible } if (visibleState.currentState || visibleState.targetState || !visibleState.isIdle) { Box(modifier = Modifier.fillMaxSize()) { Box( modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.scrim) ) AnimatedVisibility( modifier = Modifier.align(Alignment.BottomCenter), visibleState = visibleState, enter = slideInVertically( initialOffsetY = { it }, animationSpec = tween(durationMillis = 500, delayMillis = 250) ), exit = slideOutVertically( targetOffsetY = { it }, animationSpec = tween(500) ) ) { Box( modifier = modifier .clip(shape) .background(color = backgroundColor) ){ content() } } } }
CustomSheet
@Composable fun CustomSheet( viewModel: ChangelogViewModel = hiltViewModel(), navController: NavController ) { val navBackStackEntry by navController.currentBackStackEntryAsState() val currentDestination = navBackStackEntry?.destination // display only in homeScreen val isHomeScreen = currentDestination?.hierarchy?.any { it.route in listOf( NavigationBarEnum.HOME.route.name(), ) } == true val uiState by viewModel.uiState.collectAsStateWithLifecycle() var delayedVisible by remember { mutableStateOf(false) } LaunchedEffect(uiState.changelog, uiState.visible, isHomeScreen) { val showChangelog = uiState.changelog != null && uiState.visible && isHomeScreen if (showChangelog) { delay(ANIMATION_LONG_DELAY_DURATION.toLong()) delayedVisible = true } else { delayedVisible = false } } uiState.changelog?.let { changelogItem -> CustomSheetWrapper( visible = delayedVisible, modifier = Modifier .fillMaxWidth() .fillMaxHeight(0.85f), ) { ChangelogContent( currentVersion = changelogItem.version, changelog = changelogItem.changelog, onSubmit = { viewModel.onChangelogDismissed() }) } } }
MainActivity调用
NavHost(navController = navController) CustomSheet(navController = navController)
HomeScreen的ClickableCard
@Composable fun ClickableCard(data: Data?, onClick: () -> Unit) { val painter = if (diocese != null) painterResource(diocese.image) else painterResource(R.drawable.example) Card( shape = RoundedCornerShape(Dimens.roundedCornersLarge), elevation = CardDefaults.elevatedCardElevation(Dimens.roundedCornersLarge), colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface), modifier = Modifier .fillMaxWidth() .aspectRatio(16f / 9f) ) {} }
解决思路
核心问题是上层遮罩没有消费点击事件,Composable默认不会自动拦截底层交互,需要手动添加点击拦截逻辑:
给全屏遮罩添加点击拦截
在CustomSheetWrapper的全屏背景Box中,添加pointerInput修饰符来消费点击事件,阻止事件透传到下层组件:Box( modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.scrim) .pointerInput(Unit) { detectTapGestures { // 空实现,仅用于拦截点击事件 } } )也可以用
Modifier.clickable(enabled = true) { }替代,但pointerInput更适合这种仅拦截无需响应的场景,性能更优。确认层级与遮罩范围
- 你在MainActivity中先渲染
NavHost再渲染CustomSheet的顺序是正确的,Composable按代码顺序绘制,后渲染的组件会处于上层。 - 遮罩层使用
fillMaxSize()确保完全覆盖屏幕,这个设置没问题,只要点击拦截逻辑生效,就能阻挡所有底层交互。
- 你在MainActivity中先渲染
动画过程中的交互拦截
你当前的代码已经通过visibleState.currentState || visibleState.targetState || !visibleState.isIdle判断,确保遮罩在动画过程中也会显示,配合点击拦截,动画期间底层交互也会被阻挡。
内容的提问来源于stack exchange,提问作者Stanisław Olszak
相关产品推荐
相关产品推荐

