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。

相关推荐
Ai-_Man8 分钟前
您您这可以把Microsofat Copilot的多个会话比如说。左侧的多个会话一次性导出吗?不是单条会话里面的多次会对话。AI导出鸭
javascript·人工智能·ai·小程序·电脑·copilot
dadaobusi13 分钟前
pcie原子操作和nvme原子操作
学习
sunshine22 girl26 分钟前
Java学习五 面向对象高级5 内部类1
java·学习
96245635 分钟前
从“一键创建 ECS”到完整恢复状态机:ACK 抢占式节点恢复 Android 工具升级复盘
android
HwJack201 小时前
【共创稿事节】HarmonyOS 7空间排布原则:视锥、舒适区与可达性
microsoft·华为·harmonyos
blanks20202 小时前
如何查看 drift 在 本机的 sqlite 文件路径
flutter
李游Leo2 小时前
HarmonyOS 7 Core Vision Kit + Image Kit:超分批处理的 PixelMap 预算、失败降级与结果原子替换【鸿蒙心迹】
harmonyos
李游Leo2 小时前
HarmonyOS 7 AbilityAccessCtrl + ArkUI:权限弹窗重入治理与提审证据链【鸿蒙心迹】
华为·harmonyos
每天题库3 小时前
架子工脚手架工题库:安全操作考点与模拟题练习指南
学习·安全·考试·题库·考证
李游Leo3 小时前
HarmonyOS 7 Spatial Recon Kit + Preferences:重建会话中断恢复与脏任务回收【鸿蒙心迹】
华为·harmonyos