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

Jetpack Compose实现顶部弹出Modal Sheet方案咨询

实现顶部弹出Modal Sheet方案

核心思路

ModalBottomSheet是专为底部弹窗设计的组件,无法直接适配顶部弹出场景。我们可以通过AnimatedVisibility结合自定义动画,手动实现从顶部滑入/滑出+淡入淡出的效果,同时处理遮罩交互、关闭逻辑等细节。

完整代码示例

import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Scaffold
import androidx.compose.material.Surface
import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Close
import androidx.compose.material.icons.filled.Menu
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp

@Composable
fun TopModalSheetDemo() {
    var isSheetVisible by remember { mutableStateOf(false) }

    Scaffold(
        topBar = {
            androidx.compose.material.TopAppBar(
                title = { Text("顶部弹窗演示") },
                navigationIcon = {
                    IconButton(onClick = { isSheetVisible = true }) {
                        Icon(Icons.Default.Menu, contentDescription = "打开菜单")
                    }
                }
            )
        }
    ) { paddingValues ->
        Box(
            modifier = Modifier
                .fillMaxSize()
                .padding(paddingValues)
        ) {
            // 主内容区域
            Column(
                modifier = Modifier.fillMaxSize(),
                horizontalAlignment = Alignment.CenterHorizontally
            ) {
                Text("主页面内容")
            }

            // 遮罩层:弹窗显示时触发,点击可关闭弹窗
            AnimatedVisibility(visible = isSheetVisible) {
                Box(
                    modifier = Modifier
                        .fillMaxSize()
                        .background(Color.Black.copy(alpha = 0.5f))
                        .clickable { isSheetVisible = false }
                )
            }

            // 顶部弹窗主体
            AnimatedVisibility(
                visible = isSheetVisible,
                enter = slideInVertically(
                    initialOffsetY = { -it }, // 从顶部外部滑入
                    animationSpec = tween(durationMillis = 300)
                ) + fadeIn(animationSpec = tween(300)),
                exit = slideOutVertically(
                    targetOffsetY = { -it }, // 滑回顶部外部
                    animationSpec = tween(durationMillis = 300)
                ) + fadeOut(animationSpec = tween(300))
            ) {
                Surface(
                    modifier = Modifier.fillMaxWidth(),
                    shape = RoundedCornerShape(bottomStart = 16.dp, bottomEnd = 16.dp),
                    elevation = 8.dp
                ) {
                    Column(
                        modifier = Modifier
                            .fillMaxWidth()
                            .background(MaterialTheme.colors.surface)
                            .padding(16.dp)
                    ) {
                        // 关闭按钮
                        Box(modifier = Modifier.fillMaxWidth()) {
                            IconButton(
                                onClick = { isSheetVisible = false },
                                modifier = Modifier.align(Alignment.CenterEnd)
                            ) {
                                Icon(Icons.Default.Close, contentDescription = "关闭弹窗")
                            }
                        }

                        // 弹窗自定义内容
                        Text("这是顶部弹出的Modal Sheet")
                        Text("可自由添加任意布局元素")
                        // 更多内容示例...
                    }
                }
            }
        }
    }
}

关键细节说明

  • 动画组合:通过AnimatedVisibility的enter/exit参数,组合滑动动画与淡入淡出动画,可通过tween调整动画时长,适配不同设计节奏。
  • 遮罩交互:单独的AnimatedVisibility实现半透明遮罩,点击遮罩可触发弹窗关闭,遮罩会随弹窗同步显示/隐藏。
  • 布局适配:用Surface实现圆角与阴影效果,符合Material Design规范,弹窗内部布局可完全自定义。
  • 状态控制:通过remember+mutableStateOf维护弹窗可见状态,点击图标、关闭按钮、遮罩均可切换状态。

扩展优化方向

  • 若需弹窗高度灵活调整,可结合Modifier.height与rememberScrollState实现滚动布局。
  • 支持手势滑动关闭:通过pointerInput监听触摸事件,实现向下滑动弹窗触发关闭逻辑。
  • 自定义遮罩透明度、动画曲线,匹配产品设计风格。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 05:52:12