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

相关推荐
tyqtyq221 小时前
求职信生成:AI 智能求职信撰写系统的鸿蒙实现
人工智能·学习·华为·生活·harmonyos
MartinYeung52 小时前
[论文学习]PrivacyLens:评估语言模型在行动中的隐私规范意识
人工智能·学习·语言模型
l134062082352 小时前
鸿蒙实战:RDB 数据库设计与 DatabaseService
数据库·华为·harmonyos·鸿蒙系统
凉、介3 小时前
Virtio 系列(一):框架概览
笔记·学习·嵌入式·虚拟化·virtio
YSoup3 小时前
Android Stuidio中下载TRAE插件依赖的JCEF后打不开
android
ZZZMMM.zip3 小时前
数据侦探社-数据趋势分析的HarmonyOS开发实践
人工智能·华为·harmonyos·鸿蒙·鸿蒙系统
仙人球部落3 小时前
-python-LangGraph框架(3-31-LangGraph 「合并式状态管理」的原理与实践)
开发语言·javascript·python
2401_894915533 小时前
Geo搜索优化排名源码部署搭建全流程详解
android·开发语言·flask·php·精选
小心亦新4 小时前
STM32学习12--串口2收发数据包
学习