前言

Flutter是Google开发的开源UI工具包,支持用一套代码构建iOSAndroidWebWindowsmacOSLinux六大平台应用,实现"一次编写,多处运行"。

OpenHarmony是由开放原子开源基金会运营的分布式操作系统,为全场景智能设备提供统一底座,具有多设备支持、模块化设计、分布式能力和开源开放等特性。

Flutter for OpenHarmony技术方案使开发者能够:

  1. 复用Flutter现有代码(Skia渲染引擎、热重载、丰富组件库)
  2. 快速构建符合OpenHarmony规范的UI
  3. 降低多端开发成本
  4. 利用Dart生态插件资源加速生态建设

先看效果

在这里插入图片描述
在鸿蒙真机 上模拟器上成功运行后的效果

在这里插入图片描述

新增的 UI 组件与弹窗工具做成了三个可复用单元:一个负责背景氛围(GradientBackground)、一个负责按钮交互手感(CoolButton)、一个负责把底部弹出菜单封装成稳定 API(bottom_sheet_utils)。你在业务里通常只需要关心两件事:把组件放到合适的位置,以及把参数(尤其是回调和数据)传对,剩下的动效、样式一致性就交给组件本身。

  • GradientBackground:给页面铺一个干净的渐变底色 + 自动处理安全区(见 [gradient_background.dart](file:///d:/demo3/lib/widgets/gradient_background.dart))
  • CoolButton:按钮按下去有反馈,支持 Loading 防连点(见 [cool_button.dart](file:///d:/demo3/lib/widgets/cool_button.dart))
  • bottom_sheet_utils:把 showModalBottomSheet 包成稳定 API,列表/网格两套样式都能复用(见 [bottom_sheet_utils.dart](file:///d:/demo3/lib/widgets/bottom_sheet_utils.dart))

业务里大多只要做两件事:摆对位置传对参数(尤其是回调和数据源)。动画、圆角、阴影、水波纹这些“手感细节”,交给组件兜底就好。

目录


GradientBackground

文件位置

[gradient_background.dart](file:///d:/demo3/lib/widgets/gradient_background.dart)

开发要点

GradientBackground 就一个任务:铺底。它不是页面模板,不会帮你排版,更不会夹带业务逻辑。你把它放在 Scaffold.body 最外层:先把渐变铺满整屏,再用 SafeArea 把内容托起来,避免刘海/状态栏/底部手势条把 UI “啃掉一口”。

我把这层抽出来,纯粹是为了省心:不想每个页面都重复写 Container + BoxDecoration + SafeArea,更不想哪天要调个渐变颜色还得全局搜一圈。

  • 组件职责单一:只负责“背景渐变 + 安全区”,不掺业务逻辑
  • 使用 Container + LinearGradient 实现柔和底色
  • SafeArea 包裹 child,避免内容贴边(刘海/状态栏/手势条)

对外参数

  • child:内容区域(必传)

使用方式

用法很简单:把它当作 Scaffold.body 的最外层。真正的布局(Center / Column / ListView)都放到 child 里。页面需要滚动也没关系:背景在外面,滚动在里面,背景就不会“滚没了”。

import 'package:flutter/material.dart';
import '../widgets/gradient_background.dart';

class ExamplePage extends StatelessWidget {
  const ExamplePage({super.key});

  
  Widget build(BuildContext context) {
    return Scaffold(
      body: GradientBackground(
        child: Center(
          child: Text(
            'Hello',
            style: Theme.of(context).textTheme.headlineMedium,
          ),
        ),
      ),
    );
  }
}

CoolButton

文件位置

[cool_button.dart](file:///d:/demo3/lib/widgets/cool_button.dart)

开发要点

CoolButton 解决的不是“好看”,而是“好按”。有些 Demo 的按钮看着像按钮,但按下去没确认感,用户会下意识再点两下,然后你就收获了两个弹层、三次提交、四个请求……场面很热闹。

这个按钮里我做了三件小事,合起来手感就稳:

  • InkWell 做水波纹反馈(Material 兜住 ripple,不会丢)
  • onHighlightChanged 驱动一个 120ms 的缩放动画(1.0 → 0.92),按下像“轻轻压一下”
  • isLoading 时禁用点击并显示进度圈,专治连点

对外参数

  • text:按钮文案(必传)
  • onPressed:点击回调(必传)
  • isLoading:加载态(可选,默认 false)

使用方式

建议的用法是:Loading 由页面状态控制,按钮只负责展示。比如点击后先把 _loading = true,弹层返回/异步结束再设回 false。用户能明确感觉到:我点了,而且系统真的收到了。

CoolButton(
  text: '操作列表样式',
  isLoading: _loading,
  onPressed: _openActionSheet,
)

BottomSheet 工具封装(bottom_sheet_utils)

文件位置

[bottom_sheet_utils.dart](file:///d:/demo3/lib/widgets/bottom_sheet_utils.dart)

核心 API

这部分是项目里复用价值最高的东西:我把 showModalBottomSheet 的“配置项 + 样式 + 结构”揉成了两个函数。页面侧只需要给 titleoptions,就能弹出一个手感一致的底部菜单。

为了让返回值更贴近业务,我把选项做成了泛型 SheetOption<T>。你可以返回 String、枚举,甚至是业务对象(比如 ActionTypeShareTarget)。调用结果是 T?:点了某项就有值;点遮罩/下滑关闭就是 null,业务层自己决定要不要当作“取消”处理。

SheetOption<T>
  • label:显示文案
  • icon:图标
  • value:返回值(泛型 T)
showActionBottomSheet<T>()

适合“列表型操作”(复制/分享/删除…),返回用户选择的 T?

showGridBottomSheet<T>()

适合“网格型入口”(图片/视频/位置…),返回用户选择的 T?

开发要点

实现上我刻意走的是“外层透明、内层自绘”的路线:

  • 外层 showModalBottomSheet:只管弹出来,背景设透明,遮罩颜色统一
  • 内层 _AnimatedSheet:圆角、阴影、上滑淡入、SafeArea 全在这一层收口

这样做的好处很实际:你以后再加第三种样式(比如带搜索的选择器)也不容易把边界/手感做歪,直接复用 _AnimatedSheet 就行。实现里顺手还做了几件“统一口径”的事:

  • isScrollControlled: true:更容易扩展高度策略
  • barrierColor:遮罩用轻黑,聚焦弹层但不压迫
  • 内容形态分离:列表内容 _ActionSheetContent,网格内容 _GridSheetContent

使用方式

用法建议就两条,很实用:

  • options 尽量写成 const(少分配对象,弹层打开更干脆)
  • 拿到 result 先判断 null(用户取消时别继续往下跑逻辑)

标题用短一点更像系统弹层;选项文字用动词开头(复制/分享/删除)也更“顺眼”。

1)列表操作(Action Sheet)
final result = await showActionBottomSheet<String>(
  context: context,
  title: '选择操作',
  options: const [
    SheetOption(label: '复制', icon: Icons.copy_rounded, value: 'copy'),
    SheetOption(label: '分享', icon: Icons.share_rounded, value: 'share'),
    SheetOption(label: '收藏', icon: Icons.bookmark_rounded, value: 'fav'),
    SheetOption(label: '删除', icon: Icons.delete_rounded, value: 'delete'),
  ],
);
2)网格快捷(Grid Sheet)
final result = await showGridBottomSheet<String>(
  context: context,
  title: '快速操作',
  options: const [
    SheetOption(label: '图片', icon: Icons.image_rounded, value: 'image'),
    SheetOption(label: '视频', icon: Icons.videocam_rounded, value: 'video'),
    SheetOption(label: '位置', icon: Icons.location_on_rounded, value: 'location'),
    SheetOption(label: '文件', icon: Icons.insert_drive_file_rounded, value: 'file'),
  ],
);

扩展建议

如果你要把它从“好用的工具”推进到“业务级组件”,我建议按这个顺序扩展:

  1. 样式分层:危险操作(删除/移除)用红色或弱化背景提示风险
  2. 结构增强:支持分组、加副标题、加分割线
  3. 交互增强:危险操作二次确认;网格入口加搜索/最近使用

不管怎么扩展,我还是建议坚持“数据驱动”:让 option 自己表达差异,UI 层少写 if/else,后期维护会轻松很多。

  • 想要“危险操作”更醒目:为 SheetOption 增加可选的颜色/样式字段(例如 destructive)
  • 想做“分组/分割线”:在 options 里引入分组模型,或提供 headerBuilder
  • 想限制最大高度:给内容外再包一层 ConstrainedBox,避免超高内容顶满屏幕

组合示例:在页面里一起用

下面就是项目演示页的组合方式:背景负责气氛,按钮负责手感,BottomSheet 工具负责“弹层一致性”。

在真实业务里,我一般会把“按钮 Loading”和“弹层调用”连成一条线:点下去先锁一下按钮(防连点),弹层返回后再解锁,然后根据返回值走不同分支。用户会觉得这套交互很“听话”:点了就有回应,选了就有结果。

return Scaffold(
  body: GradientBackground(
    child: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          CoolButton(text: '操作列表样式', onPressed: _openActionSheet, isLoading: _loading),
          const SizedBox(height: 12),
          CoolButton(text: '网格快捷样式', onPressed: _openGridSheet, isLoading: _loading),
        ],
      ),
    ),
  ),
);

欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐