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变得更加容易!

相关推荐
恋猫de小郭11 小时前
Android Studio 正式版 10 周年回顾,承载 Androider 的峥嵘十年
android·ide·android studio
aaaweiaaaaaa14 小时前
php的使用及 phpstorm环境部署
android·web安全·网络安全·php·storm
工程师老罗16 小时前
Android记事本App设计开发项目实战教程2025最新版Android Studio
android
pengyu20 小时前
系统化掌握 Dart 编程之异常处理(二):从防御到艺术的进阶之路
android·flutter·dart
消失的旧时光-194320 小时前
android Camera 的进化
android
基哥的奋斗历程1 天前
Openfga 授权模型搭建
android·adb
Pakho love1 天前
Linux:文件与fd(被打开的文件)
android·linux·c语言·c++
勿忘初心912 天前
Android车机DIY开发之软件篇(九) NXP AutomotiveOS编译
android·arm开发·经验分享·嵌入式硬件·mcu
lingllllove2 天前
PHP中配置 variables_order详解
android·开发语言·php
消失的旧时光-19432 天前
Android-音频采集
android·音视频