AtomGit Flutter 鸿蒙客户端:ModalBottomSheet 实战

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

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

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

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

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

→ BottomSheet 是最佳选择。

二、触发方式

// 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 构建

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

四、设计要点

4.1 圆角顶部

shape:RoundedRectangleBorder(borderRadius:BorderRadius.vertical(top:Radius.circular(20)),)

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

4.2 Column 的最小高度

mainAxisSize:MainAxisSize.min

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

4.3 操作顺序

[取消] ... [编辑] [删除]

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

4.4 编辑前先关闭 BottomSheet

onPressed:(){Navigator.pop(context);// 关闭 BottomSheet_editMood(mood);// 然后切换到编辑模式},

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

五、删除确认

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

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

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

六、与 iOS 风格对比

特性Material (当前)iOS (Cupertino)
底部弹窗showModalBottomSheetshowCupertinoModalPopup
圆角RoundedRectangleBorder自带圆角
操作按钮TextButtonCupertinoActionSheet

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

小结

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


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