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

如何让自定义锚定BottomSheet仅响应其内容区域的点击与拖拽?

背景
  • 我已经实现了一个带有三个锚点的自定义底部面板(Bottom Sheet)。
  • 有一个浮动操作按钮(FAB)用于将面板从BottomSheetState.Gone切换到BottomSheetState.Half状态。
  • 底部面板的内容会以完全不覆盖、半覆盖或完全覆盖的方式叠在全屏的GoogleMap Composable之上。
  • maxHeight参数是父级Composable的完整像素高度。
@ExperimentalMaterialApi
@Composable
fun AnchoredBottomSheet(
    state: BottomSheetState,
    maxHeight: Float,
    content: @Composable () -> Unit,
) {
    val swipeableState = rememberSwipeableState(initialValue = state)

    val anchors = mapOf(
        BottomSheetState.Gone.toAnchor(maxHeight),
        BottomSheetState.Half.toAnchor(maxHeight/2),
        BottomSheetState.Full.toAnchor(0),
    )

    Column(
        modifier = Modifier
            .height(maxHeight.toDp())
            .swipeable(
                enabled = swipeableState.currentValue != BottomSheetState.Gone,
                state = swipeableState,
                orientation = Orientation.Vertical,
                anchors = anchors,
            )
            .offset {
                IntOffset(
                    0,
                    swipeableState.offset.value.roundToInt()
                )
            }
    ) {
        content()
    }
}
问题
  • 当应用处于BottomSheetState.Half或BottomSheetState.Gone状态时,
  • 用户点击屏幕上的GoogleMap区域,
  • 点击事件会被AnchoredBottomSheet拦截,无法传递给可见的GoogleMap Composable。
  • 比如在BottomSheetState.Half状态下,屏幕上方的GoogleMap区域(青色)无法接收点击事件。
问询

如何让底部面板只响应用户在其内容区域上的点击和拖拽操作?


解决方案

问题根源在于你给Column设置了height(maxHeight.toDp()),导致整个屏幕高度的区域都被这个容器占据,即使面板处于非全屏状态,上方空白区域也会拦截点击事件。可以通过以下两种方式修复:

方式一:限制可交互区域为实际内容高度

去掉Column的全屏高度设置,只让底部面板的实际内容区域成为可交互载体,空白区域自动透传点击事件给下层的GoogleMap。同时需要让content接收Modifier参数,将swipeable修饰符绑定到内容根容器:

@ExperimentalMaterialApi
@Composable
fun AnchoredBottomSheet(
    state: BottomSheetState,
    maxHeight: Float,
    content: @Composable (Modifier) -> Unit,
) {
    val swipeableState = rememberSwipeableState(initialValue = state)

    val anchors = mapOf(
        BottomSheetState.Gone.toAnchor(maxHeight),
        BottomSheetState.Half.toAnchor(maxHeight/2),
        BottomSheetState.Full.toAnchor(0),
    )

    Box(
        modifier = Modifier
            .offset {
                IntOffset(0, swipeableState.offset.value.roundToInt())
            }
    ) {
        content(
            Modifier
                .swipeable(
                    enabled = swipeableState.currentValue != BottomSheetState.Gone,
                    state = swipeableState,
                    orientation = Orientation.Vertical,
                    anchors = anchors,
                )
        )
    }
}

调用时将Modifier传给内容根容器:

AnchoredBottomSheet(state = ..., maxHeight = ...) { modifier ->
    Column(modifier = modifier.fillMaxWidth()) {
        // 底部面板内容
    }
}

方式二:通过指针事件过滤点击区域

保留原有结构,添加pointerInput修饰符判断点击位置是否在内容范围内,不在则不拦截事件:

@ExperimentalMaterialApi
@Composable
fun AnchoredBottomSheet(
    state: BottomSheetState,
    maxHeight: Float,
    content: @Composable () -> Unit,
) {
    val swipeableState = rememberSwipeableState(initialValue = state)
    val contentHeight = remember { mutableStateOf(0) }

    val anchors = mapOf(
        BottomSheetState.Gone.toAnchor(maxHeight),
        BottomSheetState.Half.toAnchor(maxHeight/2),
        BottomSheetState.Full.toAnchor(0),
    )

    Column(
        modifier = Modifier
            .height(maxHeight.toDp())
            .offset {
                IntOffset(0, swipeableState.offset.value.roundToInt())
            }
            .pointerInput(Unit) {
                awaitPointerEventScope {
                    while (true) {
                        val event = awaitPointerEvent()
                        // 判断点击是否在内容区域内
                        val isInContent = event.changes.any {
                            it.position.y <= contentHeight.value
                        }
                        if (!isInContent) {
                            // 不在内容区域,放行事件给GoogleMap
                            event.changes.forEach {
                                if (!it.isConsumed) it.positionChangeConsumed = false
                            }
                        }
                    }
                }
            }
    ) {
        Box(
            modifier = Modifier
                .onSizeChanged { contentHeight.value = it.height }
                .swipeable(
                    enabled = swipeableState.currentValue != BottomSheetState.Gone,
                    state = swipeableState,
                    orientation = Orientation.Vertical,
                    anchors = anchors,
                )
        ) {
            content()
        }
    }
}

核心逻辑都是确保只有底部面板的实际内容区域才会处理点击和拖拽事件,其余区域的事件自动传递给下层的GoogleMap。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 20:36:23