前言

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

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

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

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

先看效果

在这里插入图片描述

在鸿蒙真机 上模拟器上成功运行后的效果

在这里插入图片描述

本文详细解析了一个完整的 Flutter 顶部通知栏应用的开发过程,支持队列管理、流畅动画、毛玻璃效果、发光边框和拖拽关闭等现代化交互特性,使用 Overlay 实现覆盖层显示,易于维护和扩展。


📋 目录

项目结构说明

应用入口

演示页面 (TopNoticeDemoPage)

TopNotice 核心类

_TopNoticeManager 管理器

_TopNoticeOverlayEntry 覆盖层入口

TopNoticeCard 卡片组件


📁 项目结构说明

文件目录结构

lib/
├── main.dart                           # 应用入口文件
├── app/                                # 应用配置目录
│   └── app.dart                       # 应用主类
├── demo/                               # 演示页面目录
│   └── top_notice_demo_page.dart      # 顶部通知栏演示页面
└── overlay_notice/                     # 通知栏组件目录
    ├── top_notice.dart                 # 通知栏核心逻辑
    └── top_notice_card.dart            # 通知卡片组件

文件说明

入口文件

lib/main.dart

  • 应用入口点,包含 main() 函数
  • 定义 MyApp 类,启动应用
应用配置

lib/app/app.dart

  • DemoApp 类:应用主类
    • 配置应用主题
    • 设置路由和首页
演示页面

lib/demo/top_notice_demo_page.dart

  • TopNoticeDemoPage 类:演示页面主类
    • 展示各种类型的通知
    • 提供配置选项(持续时间、是否可关闭、是否显示操作按钮)
    • 支持批量显示通知
核心组件

lib/overlay_notice/top_notice.dart

  • TopNotice 类:通知栏静态接口
  • _TopNoticeManager 类:通知管理器(队列管理)
  • _TopNoticeOverlayEntry 类:覆盖层入口组件
  • 数据模型:TopNoticeTypeTopNoticeDataTopNoticeActionTopNoticeController

lib/overlay_notice/top_notice_card.dart

  • TopNoticeCard 组件:通知卡片 UI
    • 毛玻璃效果
    • 发光边框
    • 进度条显示
    • 关闭按钮

组件依赖关系

main.dart
  └── app/app.dart                      (导入应用配置)
      └── demo/top_notice_demo_page.dart  (导入演示页面)
          └── overlay_notice/top_notice.dart  (导入通知栏核心)
              └── overlay_notice/top_notice_card.dart  (导入通知卡片)

数据流向

  1. 触发通知:调用 TopNotice.show(context, data) 显示通知
  2. 队列管理_TopNoticeManager 将通知加入队列
  3. 覆盖层显示:创建 OverlayEntry 插入到覆盖层
  4. 动画播放_TopNoticeOverlayEntry 播放滑入动画
  5. 自动关闭:计时器到期或用户拖拽关闭
  6. 队列处理:当前通知关闭后,自动显示下一个

应用入口

1. main() 函数

import 'package:flutter/material.dart';
import 'app/app.dart';

void main() {
  runApp(const MyApp());
}

应用入口,导入应用配置。


2. MyApp 类

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

  
  Widget build(BuildContext context) {
    return const DemoApp();
  }
}

应用根组件,返回 DemoApp


演示页面 (TopNoticeDemoPage)

1. 类定义和状态管理

class TopNoticeDemoPage extends StatefulWidget {
  const TopNoticeDemoPage({super.key});

  
  State<TopNoticeDemoPage> createState() => _TopNoticeDemoPageState();
}

class _TopNoticeDemoPageState extends State<TopNoticeDemoPage>
    with SingleTickerProviderStateMixin {
  late final AnimationController _bg = AnimationController(
    vsync: this,
    duration: const Duration(seconds: 10),
  )..repeat();  // 背景动画循环播放

  Duration _duration = const Duration(seconds: 4);  // 通知持续时间
  bool _dismissible = true;  // 是否可关闭
  bool _withAction = true;  // 是否显示操作按钮

SingleTickerProviderStateMixin 提供动画控制器所需的 vsync_bg 控制背景动画,其他变量控制通知行为。


2. 显示通知

void _show(TopNoticeType type) {
  final label = switch (type) {
    TopNoticeType.success => 'Success',
    TopNoticeType.info => 'Info',
    TopNoticeType.warning => 'Warning',
    TopNoticeType.error => 'Error',
  };

  TopNotice.show(
    context,
    TopNoticeData(
      type: type,  // 通知类型
      title: '$label · 顶部 Overlay 通知',
      message: '支持队列、滑入动画、毛玻璃、发光边框、拖拽上滑关闭。',
      duration: _duration,  // 持续时间
      dismissible: _dismissible,  // 是否可关闭
      action: _withAction
          ? TopNoticeAction(
              label: 'Action',
              leading: Icons.bolt_rounded,
              onPressed: () {
                // 点击操作按钮可以触发新的通知
                TopNotice.show(
                  context,
                  const TopNoticeData(
                    type: TopNoticeType.success,
                    title: 'Action 已触发',
                    message: '这是一个由 action 触发的二次通知(队列展示)。',
                    duration: Duration(seconds: 2),
                  ),
                );
              },
            )
          : null,
      onTap: () {
        // 点击通知的回调
      },
    ),
  );
}

_show() 方法根据类型显示通知。使用 switch 表达式获取标签,TopNotice.show() 显示通知。操作按钮可以触发新的通知,实现队列展示。


3. 批量显示

void _burst() {
  final types = [
    TopNoticeType.info,
    TopNoticeType.success,
    TopNoticeType.warning,
    TopNoticeType.error,
    TopNoticeType.info,
  ];
  for (final t in types) {
    _show(t);  // 快速连续显示多个通知
  }
}

_burst() 方法快速连续显示多个通知,演示队列功能。


TopNotice 核心类

1. TopNoticeType 枚举

enum TopNoticeType { success, info, warning, error }

通知类型枚举,定义四种类型:成功、信息、警告、错误。


2. TopNoticeData 数据类

class TopNoticeData {
  const TopNoticeData({
    required this.title,        // 标题(必填)
    this.message,                // 消息内容(可选)
    this.type = TopNoticeType.info,  // 类型(默认 info)
    this.duration = const Duration(seconds: 4),  // 持续时间
    this.action,                 // 操作按钮(可选)
    this.onTap,                  // 点击回调(可选)
    this.dismissible = true,     // 是否可关闭
    this.haptic = true,          // 是否触觉反馈
  });

  final String title;
  final String? message;
  final TopNoticeType type;
  final Duration duration;  // Duration.zero 表示不自动关闭
  final TopNoticeAction? action;
  final VoidCallback? onTap;
  final bool dismissible;
  final bool haptic;
}

通知数据类,包含所有配置参数。durationDuration.zero 时不自动关闭。


3. TopNoticeAction 操作类

class TopNoticeAction {
  const TopNoticeAction({
    required this.label,      // 按钮文字
    required this.onPressed,   // 点击回调
    this.leading,             // 前置图标(可选)
  });

  final String label;
  final VoidCallback onPressed;
  final IconData? leading;
}

操作按钮数据类,定义按钮的文字、回调和图标。


4. TopNoticeController 控制器

class TopNoticeController {
  TopNoticeController._(this._token, this._dismiss);
  final Object _token;  // 唯一标识
  final void Function(Object token) _dismiss;  // 关闭方法

  void dismiss() => _dismiss(_token);  // 手动关闭通知
}

通知控制器,提供手动关闭通知的能力。通过 token 标识特定的通知。


5. TopNotice 静态类

class TopNotice {
  static final _TopNoticeManager _manager = _TopNoticeManager();

  static TopNoticeController show(BuildContext context, TopNoticeData data) {
    return _manager.enqueue(context, data);  // 加入队列并显示
  }

  static void dismissAll() => _manager.dismissAll();  // 关闭所有通知
}

通知栏静态接口,提供 show() 显示通知和 dismissAll() 关闭所有通知。


_TopNoticeManager 管理器

1. 队列管理

class _TopNoticeManager {
  final Queue<_PendingNotice> _queue = ListQueue<_PendingNotice>();  // 待显示队列
  OverlayEntry? _entry;  // 当前覆盖层入口
  _TopNoticeOverlayEntryState? _entryState;  // 当前状态
  bool _isShowing = false;  // 是否正在显示
  Object? _currentToken;  // 当前通知的 token

  TopNoticeController enqueue(BuildContext context, TopNoticeData data) {
    final token = Object();  // 生成唯一标识
    _queue.add(_PendingNotice(context, data, token));  // 加入队列
    _pump();  // 尝试显示
    return TopNoticeController._(token, _dismissByToken);
  }

管理器使用队列存储待显示的通知。enqueue() 将通知加入队列并尝试显示。


2. 通知显示

void _pump() {
  if (_isShowing) return;  // 正在显示,等待
  if (_queue.isEmpty) return;  // 队列为空,返回

  final pending = _queue.removeFirst();  // 取出第一个
  final overlay = Overlay.maybeOf(pending.context, rootOverlay: true);
  if (overlay == null) {
    // 覆盖层未准备好,重新加入队列,下一帧再试
    _queue.addFirst(pending);
    WidgetsBinding.instance.addPostFrameCallback((_) => _pump());
    return;
  }

  _isShowing = true;
  _currentToken = pending.token;
  _entry = OverlayEntry(
    builder: (context) {
      return _TopNoticeOverlayEntry(
        data: pending.data,
        onDismissed: _onDismissed,  // 关闭回调
        captureState: (s) => _entryState = s,  // 保存状态引用
      );
    },
  );
  overlay.insert(_entry!);  // 插入覆盖层
}

_pump() 方法从队列取出通知并显示。如果覆盖层未准备好,延迟到下一帧再试。


3. 通知关闭

void _onDismissed() {
  _entryState = null;
  _entry?.remove();  // 移除覆盖层
  _entry = null;
  _isShowing = false;
  _currentToken = null;
  _pump();  // 显示下一个
}

void dismissAll() {
  _queue.clear();  // 清空队列
  _entryState?.dismiss();  // 关闭当前显示的通知
}

void _dismissByToken(Object token) {
  // 如果当前显示的通知匹配,关闭它;否则从队列中移除
  if (_isShowing && _currentToken == token) {
    _entryState?.dismiss();
    return;
  }
  _queue.removeWhere((p) => p.token == token);
}

_onDismissed() 在通知关闭后调用,清理状态并显示下一个。dismissAll() 关闭所有通知,_dismissByToken() 根据 token 关闭特定通知。


_TopNoticeOverlayEntry 覆盖层入口

1. 类定义和动画

class _TopNoticeOverlayEntry extends StatefulWidget {
  const _TopNoticeOverlayEntry({
    required this.data,
    required this.onDismissed,
    required this.captureState,
  });

  final TopNoticeData data;
  final VoidCallback onDismissed;
  final ValueChanged<_TopNoticeOverlayEntryState> captureState;

  
  State<_TopNoticeOverlayEntry> createState() => _TopNoticeOverlayEntryState();
}

class _TopNoticeOverlayEntryState extends State<_TopNoticeOverlayEntry>
    with TickerProviderStateMixin {
  late final AnimationController _enter = AnimationController(
    vsync: this,
    duration: const Duration(milliseconds: 420),  // 进入动画时长
    reverseDuration: const Duration(milliseconds: 260),  // 退出动画时长
  );
  late final CurvedAnimation _curve = CurvedAnimation(
    parent: _enter,
    curve: Curves.easeOutCubic,  // 进入曲线
    reverseCurve: Curves.easeInCubic,  // 退出曲线
  );

  AnimationController? _life;  // 生命周期动画(进度条)
  Timer? _fallbackTimer;  // 备用计时器

  // 拖拽相关
  double _dragDy = 0;  // 拖拽偏移量
  bool _dragging = false;  // 是否正在拖拽
  bool _dismissing = false;  // 是否正在关闭

TickerProviderStateMixin 提供多个动画控制器所需的 vsync_enter 控制进入/退出动画,_life 控制进度条,_dragDy 记录拖拽偏移。


2. 生命周期管理


void initState() {
  super.initState();
  widget.captureState(this);  // 保存状态引用给管理器
  _enter.forward();  // 播放进入动画
  if (widget.data.haptic) {
    HapticFeedback.lightImpact();  // 触觉反馈
  }

  final d = widget.data.duration;
  if (d > Duration.zero) {
    _life = AnimationController(vsync: this, duration: d)..forward();
    // 进度完成时自动关闭
    _life!.addStatusListener((s) {
      if (s == AnimationStatus.completed) dismiss();
    });
    // 备用计时器,避免边缘情况
    _fallbackTimer = Timer(d + const Duration(milliseconds: 80), dismiss);
  }
}

void dismiss() {
  if (_dismissing) return;  // 防止重复关闭
  _dismissing = true;

  _fallbackTimer?.cancel();
  _fallbackTimer = null;
  _life?.dispose();
  _life = null;

  _enter.reverse().whenComplete(() {
    if (mounted) widget.onDismissed();  // 通知管理器
  });
}

initState() 初始化动画和计时器。dismiss() 关闭通知,播放退出动画后通知管理器。


3. 拖拽手势处理

GestureDetector(
  behavior: HitTestBehavior.opaque,
  onTap: () {
    widget.data.onTap?.call();  // 点击通知
  },
  onVerticalDragStart: widget.data.dismissible
      ? (_) => setState(() {
            _dragging = true;
            _dragDy = 0;
          })
      : null,
  onVerticalDragUpdate: widget.data.dismissible
      ? (d) => setState(() {
            _dragDy = (_dragDy + d.delta.dy).clamp(-120.0, 48.0);  // 限制范围
          })
      : null,
  onVerticalDragEnd: widget.data.dismissible
      ? (d) {
          final v = d.primaryVelocity ?? 0;  // 获取速度
          final shouldDismiss = _dragDy < -42 || v < -700;  // 判断是否关闭
          if (shouldDismiss) {
            dismiss();
            return;
          }
          setState(() {
            _dragging = false;
            _dragDy = 0;  // 恢复位置
          });
        }
      : null,

拖拽手势处理:onVerticalDragStart 开始拖拽,onVerticalDragUpdate 更新偏移,onVerticalDragEnd 根据偏移和速度决定是否关闭。


4. 动画构建

AnimatedBuilder(
  animation: _curve,
  builder: (context, child) {
    final slideY = Tween<double>(begin: -28, end: 0).transform(_curve.value);  // 滑入距离
    final fade = _curve.value;  // 透明度
    return Opacity(
      opacity: fade,
      child: Transform.translate(
        offset: Offset(0, slideY + (_dragging ? _dragDy : 0)),  // 滑入 + 拖拽偏移
        child: child,
      ),
    );
  },
  child: TopNoticeCard(...),
)

AnimatedBuilder 监听动画,实现滑入和淡入效果。拖拽时叠加拖拽偏移。


TopNoticeCard 卡片组件

1. 卡片装饰

class TopNoticeCard extends StatelessWidget {
  const TopNoticeCard({
    super.key,
    required this.data,
    required this.progress,  // 进度动画控制器
    required this.onClose,  // 关闭回调
  });

  
  Widget build(BuildContext context) {
    final scheme = Theme.of(context).colorScheme;
    final palette = _Palette.fromType(data.type, scheme);  // 根据类型获取颜色

    final width = MediaQuery.sizeOf(context).width;
    final cardWidth = width >= 520 ? 520.0 : width - 24.0;  // 响应式宽度

    return ConstrainedBox(
      constraints: BoxConstraints.tightFor(width: cardWidth),
      child: DecoratedBox(
        decoration: BoxDecoration(
          borderRadius: BorderRadius.circular(18),
          gradient: LinearGradient(
            colors: [
              palette.borderA.withAlpha(_a(0.95)),  // 渐变边框
              palette.borderB.withAlpha(_a(0.70)),
              palette.borderC.withAlpha(_a(0.85)),
            ],
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
          ),
          boxShadow: [
            BoxShadow(
              color: palette.glow.withAlpha(_a(0.30)),  // 发光效果
              blurRadius: 26,
              spreadRadius: 1,
              offset: const Offset(0, 10),
            ),
            BoxShadow(
              color: Colors.black.withAlpha(_a(0.18)),  // 阴影
              blurRadius: 26,
              offset: const Offset(0, 14),
            ),
          ],
        ),
        child: Padding(
          padding: const EdgeInsets.all(1.2),  // 边框宽度
          child: ClipRRect(
            borderRadius: BorderRadius.circular(17),
            child: BackdropFilter(
              filter: ImageFilter.blur(sigmaX: 18, sigmaY: 18),  // 毛玻璃效果
              child: DecoratedBox(
                decoration: BoxDecoration(
                  color: palette.surface.withAlpha(_a(0.62)),
                  gradient: LinearGradient(
                    colors: [
                      palette.surface.withAlpha(_a(0.65)),
                      palette.surface.withAlpha(_a(0.48)),
                    ],
                    begin: Alignment.topCenter,
                    end: Alignment.bottomCenter,
                  ),
                ),

卡片使用渐变边框、发光阴影和毛玻璃效果。BackdropFilter 实现毛玻璃,DecoratedBox 实现渐变背景。


2. 内容布局

child: _Content(
  data: data,
  palette: palette,
  progress: progress,
  onClose: onClose,
),

内容组件显示标题、消息、进度条、操作按钮和关闭按钮。


使用示例

基本使用

// 显示一个简单的信息通知
TopNotice.show(
  context,
  const TopNoticeData(
    type: TopNoticeType.info,
    title: '提示',
    message: '这是一条信息通知',
    duration: Duration(seconds: 3),
  ),
);

// 显示成功通知,带操作按钮
TopNotice.show(
  context,
  TopNoticeData(
    type: TopNoticeType.success,
    title: '操作成功',
    message: '数据已保存',
    duration: const Duration(seconds: 4),
    action: TopNoticeAction(
      label: '查看',
      leading: Icons.visibility,
      onPressed: () {
        // 处理操作
      },
    ),
  ),
);

// 显示错误通知,不可关闭
TopNotice.show(
  context,
  const TopNoticeData(
    type: TopNoticeType.error,
    title: '错误',
    message: '操作失败,请重试',
    duration: Duration(seconds: 5),
    dismissible: false,  // 不可手动关闭
  ),
);

// 手动关闭通知
final controller = TopNotice.show(context, data);
// 稍后关闭
controller.dismiss();

// 关闭所有通知
TopNotice.dismissAll();

使用步骤:

  1. 调用 TopNotice.show() 显示通知
  2. 传入 TopNoticeData 配置通知内容
  3. 可选:使用返回的 TopNoticeController 手动关闭
  4. 可选:调用 TopNotice.dismissAll() 关闭所有通知

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

Logo

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

更多推荐