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

Jetpack Compose中能否实现嵌套ModalBottomSheetLayout以保留多层弹窗?

实现嵌套多层ModalBottomSheetLayout(支持N层共存)

默认的ModalBottomSheetLayout不支持直接嵌套,因为每个实例独立管理自身的弹窗状态。要实现多层弹窗共存且不关闭之前的弹窗,我们可以通过维护弹窗状态列表并堆叠渲染弹窗的方式来实现。

核心思路

  1. 定义数据类存储每个弹窗的状态和内容
  2. 使用可变状态列表跟踪所有活跃的弹窗
  3. 自定义容器组件,将所有弹窗堆叠渲染,通过偏移量让下层弹窗部分可见
  4. 管理弹窗的打开/关闭逻辑,确保状态正确更新

完整实现代码

1. 定义弹窗条目数据类

import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.SheetState
import java.util.UUID

@OptIn(ExperimentalMaterialApi::class)
data class SheetEntry(
    val id: String = UUID.randomUUID().toString(),
    val sheetState: SheetState,
    val content: @Composable () -> Unit
)

2. 自定义嵌套弹窗容器

import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.offset
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.ModalBottomSheetLayout
import androidx.compose.material.RoundedCornerShape
import androidx.compose.material.SheetState
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun NestedBottomSheetContainer(
    sheets: List<SheetEntry>,
    mainContent: @Composable () -> Unit
) {
    Box(modifier = Modifier.fillMaxSize()) {
        // 渲染主内容
        mainContent()
        
        // 堆叠渲染所有弹窗
        sheets.forEachIndexed { index, sheet ->
            key(sheet.id) {
                val offsetY = index * 40.dp // 控制下层弹窗可见高度
                ModalBottomSheetLayout(
                    sheetState = sheet.sheetState,
                    // 仅顶层弹窗显示遮罩
                    scrimColor = if (index == sheets.lastIndex) Color.Black.copy(alpha = 0.5f) else Color.Transparent,
                    // 弹窗层级越高,阴影越明显
                    sheetElevation = 4.dp + index * 2.dp,
                    sheetShape = RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp),
                    sheetContent = {
                        // 偏移弹窗内容,露出下层弹窗
                        Box(modifier = Modifier.offset(y = -offsetY)) {
                            sheet.content()
                        }
                    }
                ) {
                    Box(modifier = Modifier.fillMaxSize())
                }
            }
        }
    }
}

3. 使用示例(包含弹窗打开/关闭逻辑)

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.Button
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.SheetValue
import androidx.compose.material.Text
import androidx.compose.material.rememberSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Alignment
import androidx.compose.ui.unit.dp
import kotlinx.coroutines.launch

@OptIn(ExperimentalMaterialApi::class)
@Composable
fun NestedBottomSheetDemo() {
    val sheetEntries = remember { mutableStateListOf<SheetEntry>() }
    val coroutineScope = rememberCoroutineScope()

    // 打开新弹窗
    fun openNewSheet(content: @Composable () -> Unit) {
        val newSheetState = rememberSheetState(initialValue = SheetValue.Collapsed)
        val newEntry = SheetEntry(
            sheetState = newSheetState,
            content = content
        )
        sheetEntries.add(newEntry)
        // 自动展开新弹窗
        coroutineScope.launch {
            newSheetState.expand()
        }
    }

    // 关闭指定弹窗
    fun closeSheet(entry: SheetEntry) {
        coroutineScope.launch {
            entry.sheetState.collapse()
            sheetEntries.remove(entry)
        }
    }

    NestedBottomSheetContainer(
        sheets = sheetEntries,
        mainContent = {
            MainScreen(
                onOpenSheet = {
                    openNewSheet {
                        Screen1(
                            onOpenNextSheet = {
                                openNewSheet {
                                    Screen2(onClose = {
                                        sheetEntries.lastOrNull()?.let { closeSheet(it) }
                                    })
                                }
                            },
                            onClose = {
                                sheetEntries.lastOrNull()?.let { closeSheet(it) }
                            }
                        )
                    }
                }
            )
        }
    )
}

// 主界面
@Composable
fun MainScreen(onOpenSheet: () -> Unit) {
    Column(
        modifier = Modifier.fillMaxSize(),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Button(onClick = onOpenSheet) {
            Text("打开第一层弹窗")
        }
    }
}

// 第一层弹窗内容
@Composable
fun Screen1(onOpenNextSheet: () -> Unit, onClose: () -> Unit) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("弹窗1内容")
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = onOpenNextSheet) {
            Text("打开第二层弹窗")
        }
        Spacer(modifier = Modifier.height(8.dp))
        Button(onClick = onClose) {
            Text("关闭弹窗1")
        }
    }
}

// 第二层弹窗内容
@Composable
fun Screen2(onClose: () -> Unit) {
    Column(
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp),
        verticalArrangement = Arrangement.Center,
        horizontalAlignment = Alignment.CenterHorizontally
    ) {
        Text("弹窗2内容")
        Spacer(modifier = Modifier.height(16.dp))
        Button(onClick = onClose) {
            Text("关闭弹窗2")
        }
    }
}

关键细节说明

  • 弹窗堆叠偏移:通过offsetY = index * 40.dp控制每个弹窗的向上偏移,让下层弹窗的顶部部分可见,可根据需求调整偏移量。
  • 遮罩与阴影:仅顶层弹窗显示半透明遮罩,且层级越高阴影越重,增强视觉层次感。
  • 状态管理:使用mutableStateListOf维护弹窗列表,确保添加/移除弹窗时界面自动更新;每个弹窗的SheetState独立管理自身展开/折叠状态。
  • 关闭逻辑:关闭弹窗时先折叠再从列表中移除,保证动画流畅。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.29 21:45:08