前言:跨生态开发的新机遇

在移动开发领域,我们总是面临着选择与适配。今天,你的Flutter应用在Android和iOS上跑得正欢,明天可能就需要考虑一个新的平台:HarmonyOS(鸿蒙)。这不是一道选答题,而是很多团队正在面对的现实。

Flutter的优势很明确——写一套代码,就能在两个主要平台上运行,开发体验流畅。而鸿蒙代表的是下一个时代的互联生态,它不仅仅是手机系统,更着眼于未来全场景的体验。将现有的Flutter应用适配到鸿蒙,听起来像是一个“跨界”任务,但它本质上是一次有价值的技术拓展:让产品触达更多用户,也让技术栈覆盖更广。

不过,这条路走起来并不像听起来那么简单。Flutter和鸿蒙,从底层的架构到上层的工具链,都有着各自的设计逻辑。会遇到一些具体的问题:代码如何组织?原有的功能在鸿蒙上如何实现?那些平台特有的能力该怎么调用?更实际的是,从编译打包到上架部署,整个流程都需要重新摸索。
这篇文章想做的,就是把这些我们趟过的路、踩过的坑,清晰地摊开给你看。我们不会只停留在“怎么做”,还会聊到“为什么得这么做”,以及“如果出了问题该往哪想”。这更像是一份实战笔记,源自真实的项目经验,聚焦于那些真正卡住过我们的环节。

无论你是在为一个成熟产品寻找新的落地平台,还是从一开始就希望构建能面向多端的应用,这里的思路和解决方案都能提供直接的参考。理解了两套体系之间的异同,掌握了关键的衔接技术,不仅能完成这次迁移,更能积累起应对未来技术变化的能力。

混合工程结构深度解析

项目目录架构

当Flutter项目集成鸿蒙支持后,典型的项目结构会发生显著变化。以下是经过ohos_flutter插件初始化后的项目结构:

my_flutter_harmony_app/
├── lib/                          # Flutter业务代码(基本不变)
│   ├── main.dart                 # 应用入口
│   ├── home_page.dart           # 首页
│   └── utils/
│       └── platform_utils.dart  # 平台工具类
├── pubspec.yaml                  # Flutter依赖配置
├── ohos/                         # 鸿蒙原生层(核心适配区)
│   ├── entry/                    # 主模块
│   │   └── src/main/
│   │       ├── ets/              # ArkTS代码
│   │       │   ├── MainAbility/
│   │       │   │   ├── MainAbility.ts       # 主Ability
│   │       │   │   └── MainAbilityContext.ts
│   │       │   └── pages/
│   │       │       ├── Index.ets           # 主页面
│   │       │       └── Splash.ets          # 启动页
│   │       ├── resources/        # 鸿蒙资源文件
│   │       │   ├── base/
│   │       │   │   ├── element/  # 字符串等
│   │       │   │   ├── media/    # 图片资源
│   │       │   │   └── profile/  # 配置文件
│   │       │   └── en_US/        # 英文资源
│   │       └── config.json       # 应用核心配置
│   ├── ohos_test/               # 测试模块
│   ├── build-profile.json5      # 构建配置
│   └── oh-package.json5         # 鸿蒙依赖管理
└── README.md

目录

展示效果图片

flutter 实时预览 效果展示
在这里插入图片描述

运行到鸿蒙虚拟设备中效果展示
在这里插入图片描述

功能代码实现

LoadingOverlay 组件开发

组件设计思路

LoadingOverlay 组件是一个独立的加载弹窗工具类,采用静态方法设计模式,方便在应用的任何地方调用。该组件通过 Flutter 的 Overlay 机制实现,能够在界面顶层显示加载状态,支持自定义加载文字、颜色等样式。

核心实现代码

import 'package:flutter/material.dart';

class LoadingOverlay {
  static OverlayEntry? _overlayEntry;
  static bool _isVisible = false;

  static void show(
    BuildContext context,
    {
      String message = '加载中...',
      Color backgroundColor = Colors.black54,
      Color indicatorColor = Colors.white,
      Color textColor = Colors.white,
      double fontSize = 14.0,
    }
  ) {
    // 如果已经有加载弹窗在显示,先移除
    if (_isVisible) {
      hide();
    }

    // 创建覆盖层条目
    _overlayEntry = OverlayEntry(
      builder: (BuildContext context) {
        return Material(
          color: Colors.transparent,
          child: Container(
            color: backgroundColor,
            child: Center(
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  CircularProgressIndicator(
                    valueColor: AlwaysStoppedAnimation<Color>(indicatorColor),
                  ),
                  const SizedBox(height: 16),
                  Text(
                    message,
                    style: TextStyle(
                      color: textColor,
                      fontSize: fontSize,
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      },
    );

    // 添加覆盖层条目到当前上下文的覆盖层
    Overlay.of(context).insert(_overlayEntry!);
    _isVisible = true;
  }

  static void hide() {
    if (_isVisible && _overlayEntry != null) {
      _overlayEntry?.remove();
      _overlayEntry = null;
      _isVisible = false;
    }
  }
}

组件使用方法

LoadingOverlay 组件提供了简洁的静态方法 showhide,可以在应用的任何地方调用,示例如下:

  1. 显示默认加载弹窗
LoadingOverlay.show(
  context,
);
  1. 显示自定义加载弹窗
LoadingOverlay.show(
  context,
  message: '正在加载数据...',
  backgroundColor: Colors.black54,
  indicatorColor: Colors.blue,
  textColor: Colors.white,
  fontSize: 16.0,
);
  1. 隐藏加载弹窗
LoadingOverlay.hide();

开发注意事项

  1. Overlay 管理

    • 每次显示新的加载弹窗前,需要先检查是否已有弹窗在显示,如果有则先移除,避免多个弹窗叠加显示
    • 使用静态变量 _overlayEntry_isVisible 来管理弹窗的显示状态
  2. 参数设计

    • 提供了丰富的可选参数,包括加载文字、背景颜色、指示器颜色、文字颜色和字体大小
    • 为所有可选参数设置了合理的默认值,方便快速调用
  3. 用户交互

    • 加载弹窗显示时,会覆盖整个屏幕,用户无法操作底层界面,这是加载弹窗的预期行为
  4. 内存管理

    • 确保在不需要加载弹窗时,调用 hide() 方法正确移除 OverlayEntry,避免内存泄漏

主页面集成与使用

页面结构

主页面 MyHomePage 是一个简单的布局,包含一个按钮,点击后触发加载弹窗,显示3秒后自动隐藏。

核心实现代码

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

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

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

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter for openHarmony',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      debugShowCheckedModeBanner: false,
      home: const MyHomePage(title: 'Flutter for openHarmony'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  // 模拟加载操作
  Future<void> _simulateLoading() async {
    // 显示加载弹窗
    LoadingOverlay.show(
      context,
      message: '正在加载数据...',
    );

    // 模拟网络请求延迟
    await Future.delayed(const Duration(seconds: 3));

    // 隐藏加载弹窗
    LoadingOverlay.hide();
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: _simulateLoading,
              child: const Text('点击触发加载弹窗'),
            ),
            const SizedBox(height: 20),
            const Text(
              '加载弹窗将显示3秒后自动隐藏',
              style: TextStyle(
                fontSize: 16,
                color: Colors.grey,
              ),
            ),
          ],
        ),
      ),
    );
  }
}

使用注意事项

  1. 导入组件

    • 需要在主页面中导入 LoadingOverlay 组件:import 'widgets/loading_overlay.dart';
  2. Context 传递

    • 调用 LoadingOverlay.show() 方法时,需要传递当前的 BuildContext
  3. 异步操作

    • 加载弹窗通常用于异步操作,如网络请求、文件加载等
    • 在异步操作完成后,一定要调用 LoadingOverlay.hide() 隐藏弹窗
  4. 用户体验

    • 为加载弹窗设置合适的提示文字,让用户了解当前的加载内容
    • 避免加载时间过长,必要时可以添加取消按钮

本次开发中容易遇到的问题

  1. Overlay 管理问题

    • 问题:多次快速点击按钮时,可能会导致多个加载弹窗叠加显示,或者弹窗显示异常
    • 解决方案:在显示新弹窗前,先检查是否已有弹窗在显示,如果有则先移除,使用静态变量来管理弹窗的显示状态
  2. 内存泄漏问题

    • 问题:如果加载弹窗没有正确移除,可能会导致内存泄漏
    • 解决方案:确保在异步操作完成后,无论成功还是失败,都调用 hide() 方法正确移除 OverlayEntry 并重置状态变量
  3. Context 问题

    • 问题:在某些情况下,传递的 BuildContext 可能已经失效,导致 Overlay.of(context) 报错
    • 解决方案:确保传递的 BuildContext 是有效的,避免在已经 dispose 的 widget 中调用 show 方法
  4. 样式定制问题

    • 问题:不同应用可能需要不同样式的加载弹窗,直接修改组件代码会影响其他地方的使用
    • 解决方案:通过参数化设计,提供丰富的可选参数,支持用户自定义样式
  5. 测试问题

    • 问题:在自动化测试中,加载弹窗可能会干扰测试流程
    • 解决方案:可以添加一个测试模式,在测试环境中禁用加载弹窗的显示

总结本次开发中用到的技术点

  1. Flutter 核心概念

    • Widget 体系:使用 StatelessWidget 和 StatefulWidget 构建应用界面
    • 布局组件:使用 Center、Column、SizedBox 等布局组件组织界面结构
    • Material 组件:使用 ElevatedButton、Scaffold、AppBar 等 Material 组件构建符合 Material Design 规范的界面
  2. Overlay 机制

    • 使用 Flutter 的 Overlay 系统在界面顶层显示悬浮组件
    • 通过 OverlayEntry 管理悬浮组件的创建、插入和移除
  3. 静态工具类设计

    • 采用静态方法和静态变量设计 LoadingOverlay 工具类,方便全局调用
    • 使用私有静态变量管理组件状态,确保组件行为的一致性
  4. 参数化设计

    • 使用命名可选参数和默认值,提高组件的灵活性和易用性
    • 支持自定义多个样式参数,满足不同场景的需求
  5. 异步编程

    • 使用 Future.delayed 模拟网络请求延迟
    • 在异步操作中管理加载弹窗的显示和隐藏
  6. 组件化开发

    • 将加载弹窗功能抽离为独立的组件,提高代码的可复用性和可维护性
    • 遵循单一职责原则,使组件功能明确、易于理解和测试
  7. 代码组织

    • 按功能模块组织代码,将组件放在 widgets 目录下
    • 使用清晰的命名和注释,提高代码的可读性
  8. 用户体验设计

    • 通过加载弹窗提供视觉反馈,增强用户体验
    • 为加载弹窗设置合适的提示文字和显示时间

通过本次开发,我们实现了一个功能完整、使用灵活的加载弹窗组件,并在主页面中进行了集成和展示。这个组件不仅可以在 Flutter 应用中使用,也可以在 Flutter for OpenHarmony 项目中无缝运行,展示了 Flutter 跨平台开发的优势。

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

Logo

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

更多推荐