Android Compose如何构建阶梯式抽屉

背景

近期有个需求,需要做一个三段式抽屉组件,有3个关键状态:折叠、半展开、展开,交互效果类似:

官方Bottom Sheet组件(View版)

m3.material.io/components/... 中提到的两种BottomSheet组件,效果如下:

Standard bottom sheets Modal bottom sheets

半展开状态:SheetBehavior#setHalfExpandedRatio

详情参考官方Demo(view版本):github.com/material-co...

可以满足需求,但项目基本都迁移到Compose了,所以暂时不考虑View版本实现。

官方Bottom Sheet组件(Compose版)

Demo源码: androidx.compose.material3.samples.SimpleBottomSheetScaffoldSample

Standard bottom sheets Modal bottom sheets

Compose版本支持3种状态:

状态 描述
Hidden sheet不可见
Expanded sheet完全展开
PartiallyExpanded sheet半展开,展示高度对应下图sheetPeekHeight

折叠状态下可见高度为0,看起来并不满足我们的需求。

破局之路

控制Hidden状态显示高度的代码是私有的,代码如下:

kotlin 复制代码
@Composable
@ExperimentalMaterial3Api
fun BottomSheetScaffold(
    sheetContent: @Composable ColumnScope.() -> Unit,
    modifier: Modifier = Modifier,
    scaffoldState: BottomSheetScaffoldState = rememberBottomSheetScaffoldState(),
    sheetPeekHeight: Dp = BottomSheetDefaults.SheetPeekHeight,
    ...
) {
    val peekHeightPx = with(LocalDensity.current) {
        sheetPeekHeight.roundToPx()
    }
    BottomSheetScaffoldLayout(
        ...,
        bottomSheet = { layoutHeight ->
            StandardBottomSheet(
                ...,
                calculateAnchors = { sheetSize ->
                    val sheetHeight = sheetSize.height
                    // 关键代码
                    // 1. layoutHeight: sheet content的总高度;
                    // 2. peekHeightPx: 半展开状态的展示高度
                    DraggableAnchors {
                        if (!scaffoldState.bottomSheetState.skipPartiallyExpanded) {
                            PartiallyExpanded at (layoutHeight - peekHeightPx).toFloat()
                        }
                        if (sheetHeight != peekHeightPx) {
                            Expanded at maxOf(layoutHeight - sheetHeight, 0).toFloat()
                        }
                        if (!scaffoldState.bottomSheetState.skipHiddenState) {
                            // Hidden下偏移量等于layoutHeight,完全不可见
                            Hidden at layoutHeight.toFloat()
                        }
                    }
                },
                ...
            )
        }
    )
}

BottomSheetScaffold源码中用到了一个类DraggableAnchors,我们可以基于此自定义BottomSheet

参考官方文档:developer.android.com/develop/ui/...

自定义抽屉效果

代码如下:

kotlin 复制代码
@Composable
@Preview
fun MyBottomSheet(
    modifier: Modifier = Modifier,
) {
    val anchoredDraggableState = rememberAnchoredDraggableState()
    Box(
        modifier
            .offset {
                IntOffset(
                    x = 0,
                    y = anchoredDraggableState
                        .requireOffset()
                        .roundToInt(),
                )
            }
            .onSizeChanged {
                val anchors = DraggableAnchors {
                    DragValue.Expanded at 0.dpToPx
                    DragValue.PartiallyExpanded at it.height * 0.33f // 修改
                    DragValue.Collapsed at it.height - 200.dpToPx // 修改
                }
                anchoredDraggableState.updateAnchors(anchors)
            },
    ) {
        Column(
            modifier = Modifier
                .anchoredDraggable(
                    state = anchoredDraggableState,
                    orientation = Orientation.Vertical,
                )
                .fillMaxSize()
                .background(Color.Gray),
        ) {
            Text(text = "Hello")
        }
    }
}

private enum class DragValue { // 3阶抽屉状态,可以根据实际场景定义更多
    Collapsed,
    Expanded,
    PartiallyExpanded,
}

@OptIn(ExperimentalFoundationApi::class)
@Composable
private fun rememberAnchoredDraggableState(
    initialState: DragValue = DragValue.PartiallyExpanded,
): AnchoredDraggableState<DragValue> = remember(initialState) {
    AnchoredDraggableState(
        initialValue = DragValue.PartiallyExpanded,
        animationSpec = SpringSpec(),
        positionalThreshold = { 56.dpToPx },
        velocityThreshold = { 56.dpToPx },
    )
}

总结

本文从需求开始探索Compose版本的阶梯式抽屉效果实现,最后从BottomSheetScaffold中找到关键突破口,基于AnchoredDraggable定制出所需的抽屉效果,可以发现,在Compose体系下,定制UI变得更加容易!

相关推荐
帅得不敢出门7 分钟前
精简Android SDK(AOSP)的git项目提高git指令速度
android·java·开发语言·git·elasticsearch
2501_9371892311 分钟前
神马 9.0 2025 最新版源码系统:安全加固 + 二次开发友好
android·源码·开源软件·源代码管理·机顶盒
モンキー・D・小菜鸡儿1 小时前
Android 中 StateFlow 的使用
android·kotlin
二川bro1 小时前
字符串特性解析:Python不可变性引发的错误
android·开发语言·python
2501_937154932 小时前
酷秒神马 9.0 2025 版:微服务架构
android·源码·源代码管理·机顶盒
我又来搬代码了2 小时前
【Android】【Compose】Compose知识点复习(一)
android·前端·kotlin·android studio
fatiaozhang95272 小时前
九联UNP-SJA8-国科GK6323V100C-2+8G-安卓9.0-原厂强刷固件包-可救砖及开ADB教程
android·adb·电视盒子·刷机固件·机顶盒刷机·机顶盒刷机固件·九联unp-sja8
似霰2 小时前
传统 Hal 开发笔记4----实现 Hal 模块
android·hal
Android出海2 小时前
Google Play正式出手整治后台耗电应用
android·新媒体运营·产品运营·流量运营·用户运营
Winter_Sun灬3 小时前
CentOS7 交叉编译 ACE+TAO-6.5.13 安卓 arm64-v8a 静态库
android·ace