AtomGit Flutter 鸿蒙客户端:ModalBottomSheet 实战

底部弹窗是移动端最自然的"详情+操作"模式。本文详解 E-Brufen 中 ModalBottomSheet 的完整实现。

一、为什么是 BottomSheet 而非新页面?

方案 优点 缺点
Navigator.push 完整页面,可承载复杂内容 打断用户浏览流
AlertDialog 简单直接 缺乏操作空间
ModalBottomSheet 不打断浏览,手势关闭自然 不适合超长内容

对于"查看情绪详情"这个场景:

  • 内容量小(emoji + 标签 + 日期 + 备注)
  • 操作简单(编辑 / 删除)
  • 用户希望保持时间线的浏览上下文

→ BottomSheet 是最佳选择。

二、触发方式

dart 复制代码
// diary_page.dart --- 时间线列表项
ListTile(
  leading: Text(mood.moodType.emoji, style: TextStyle(fontSize: 32)),
  title: Text(mood.moodType.label),
  subtitle: mood.note?.isNotEmpty == true
    ? Text(mood.note!, maxLines: 1, overflow: TextOverflow.ellipsis)
    : null,
  trailing: Text(_formatDate(mood.createdAt)),
  onTap: () => _showMoodDetail(mood),  // ← 点击触发
)

三、BottomSheet 构建

dart 复制代码
void _showMoodDetail(MoodEntry mood) {
  showModalBottomSheet(
    context: context,
    shape: const RoundedRectangleBorder(
      borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
    ),
    builder: (_) => Padding(
      padding: const EdgeInsets.all(20),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('${mood.moodType.emoji}  ${mood.moodType.label}',
            style: const TextStyle(fontSize: 24)),
          const SizedBox(height: 8),
          Text(_formatDate(mood.createdAt),
            style: const TextStyle(color: Colors.grey)),
          // 备注(如果有)
          if (mood.note != null && mood.note!.isNotEmpty) ...[
            const SizedBox(height: 12),
            Text(mood.note!),
          ],
          const SizedBox(height: 16),
          // 操作按钮
          Row(
            mainAxisAlignment: MainAxisAlignment.end,
            children: [
              TextButton.icon(
                onPressed: () {
                  Navigator.pop(context);  // 先关闭 BottomSheet
                  _editMood(mood);          // 再进入编辑
                },
                icon: const Icon(Icons.edit),
                label: const Text('编辑'),
              ),
              TextButton.icon(
                onPressed: () {
                  Navigator.pop(context);
                  if (mood.id != null) _deleteMood(mood.id!);
                },
                icon: const Icon(Icons.delete, color: Colors.red),
                label: const Text('删除', style: TextStyle(color: Colors.red)),
              ),
            ],
          ),
        ],
      ),
    ),
  );
}

四、设计要点

4.1 圆角顶部

dart 复制代码
shape: RoundedRectangleBorder(
  borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
)

只给顶部圆角------这是 Material Design BottomSheet 的视觉惯例。

4.2 Column 的最小高度

dart 复制代码
mainAxisSize: MainAxisSize.min

确保 BottomSheet 的高度自适应内容,不留多余空白。

4.3 操作顺序

复制代码
[取消] ... [编辑] [删除]

编辑在左、删除在右------但通过 MainAxisAlignment.end 将所有操作按钮推到右侧。删除用红色作为危险操作的视觉警告。

4.4 编辑前先关闭 BottomSheet

dart 复制代码
onPressed: () {
  Navigator.pop(context);  // 关闭 BottomSheet
  _editMood(mood);          // 然后切换到编辑模式
},

如果先调用 _editMood(会切换 Tab),BottomSheet 会在错误的 Tab 上显示,视觉上会很怪异。

五、删除确认

dart 复制代码
void _deleteMood(int id) {
  showDialog(
    context: context,
    builder: (_) => AlertDialog(
      title: const Text('删除确认'),
      content: const Text('确定删除这条记录吗?'),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('取消'),
        ),
        TextButton(
          onPressed: () {
            Navigator.pop(context);
            widget.moodStorage.delete(id);
          },
          child: const Text('删除'),
        ),
      ],
    ),
  );
}

删除需要二次确认------这是不可逆操作的标准 UX 模式。

★ Insight ─────────────────────────────────────

交互流程是:时间线列表项 → BottomSheet(详情+操作) → AlertDialog(删除确认)。这个"渐进式揭示"(Progressive Disclosure)模式将复杂度按层次暴露------用户先看到概览,需要时才深入操作,最终只有删除才看到确认弹窗。

─────────────────────────────────────────────────

六、与 iOS 风格对比

特性 Material (当前) iOS (Cupertino)
底部弹窗 showModalBottomSheet showCupertinoModalPopup
圆角 RoundedRectangleBorder 自带圆角
操作按钮 TextButton CupertinoActionSheet

当前使用 Material 风格是为了与应用的其他 Material 3 组件保持一致。

小结

ModalBottomSheet 是移动端"不打断浏览"的经典交互模式。E-Brufen 的实现覆盖了完整的使用流程:展示详情、提供操作、确认危险操作、跨 Tab 联动编辑。约 40 行代码完成了一个自然流畅的交互闭环。


作者简介 :E-Brufen Dev,Flutter & 鸿蒙开发者,专注于跨平台移动应用开发与心理健康数字化,项目地址:AtomGit - E-Brufen

相关推荐
晴天165 分钟前
前端 postMessage 使用场景
前端·javascript·网络
zhanghaha13141 小时前
HTML系列教程:14_HTML 表单与输入框 <form>、<input> 零基础详解
java·前端·javascript
传奇开心果编程7 小时前
【xilem0.4基础语法学与练】第13课:Xilem 0.4 最简短代码体现“一切皆设计图“
学习·rust·前端框架
小雨青年7 小时前
【HarmonyOS 7 平行视界深度实战】06 页面路由、返回栈与多层跳转怎么处理
华为·harmonyos
小雪崩7 小时前
嵌入式学习 day45:51单片机基础
学习·51单片机
李少兄7 小时前
JavaScript 数据类型完全指南
开发语言·javascript·ecmascript
tianshi48518 小时前
Android 应用启动窗口(Splash Screen)的创建与销毁流程
android
脚踏实地,坚持不懈!8 小时前
ICU Calendar 实际工作问题排查手册
android
ii_best9 小时前
安卓脚本/ios开发软件按键精灵实战:自动定位弹窗广告关闭按钮坐标的通用方案
android·ios·自动化
彧azz9 小时前
Java学习语法篇:变量
java·学习