如何阻止点击穿透自定义的Compose底部弹出层遮罩?

移动开发 2026-07-09

Description

我希望我的 CustomSheet 能正确覆盖屏幕、阻止下面屏幕的所有交互。我以为这是位于顶部的任意Composable的默认行为。然而,在这种情况下,尽管整块屏幕都已被覆盖,我仍然可以点击 HomeScreen 上的按钮。你有什么想法吗?

Code details

我的 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,里面有一个按钮(该按钮被 CustomSheet 完全遮挡):

@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)
    ) {}
}

解决方案

在Jetpack Compose中,只有background() 修饰符的Box不会消费指针事件。background会绘制像素,但对输入系统保持“透明”,因此点击会穿透到Box下方绘制的内容。父级内部的叠放顺序由声明顺序决定。后面的同级会绘制在上方,但绘制顺序并不会自动拦截触摸事件。

向覆盖层Box添加任一

.clickable(
  interactionSource = remember { MutableInteractionSource() },
  indication = null,
  onClick = { /* dismiss */ },
)

.pointerInput(Unit) {
  awaitPointerEventScope {
      while (true) { awaitPointerEvent() }
  }
}

,最好也应用到sheets内容Box上,这样触摸就会被吸收,而不是穿透到下方的屏幕。

完整示例:

Box(
  modifier = Modifier
      .fillMaxSize()
      .background(MaterialTheme.colorScheme.scrim)
      .clickable(
          interactionSource = remember { MutableInteractionSource() },
          indication = null,
          onClick = onScrimClick,   // or {} to just absorb
      ),
)

你可以在文档中查看更多信息:https://developer.android.com/develop/ui/compose/touch-input/pointer-input/understand-gestures?utm_source=chatgpt.com#event-dispatching

指针事件会被分发到一个可组合项层级。当一个新的指针首次触发指针事件时,系统开始对 "eligible" 可组合项进行命中测试。一个可组合项在具备指针输入处理能力时才被视为符合条件。

站内所有文章版权归属LeftHeroAI导航站,无授权禁止任何主体转载、抄袭、复制内容,亦不得私自架设镜像站点。一经侵权,本站将通过法律途径追责。

相关文章