基于HarmonyOS API 24 Flutter for OpenHarmony 实战:按钮加载状态
前言:跨生态开发的新机遇 {#前言跨生态开发的新机遇}
在移动开发领域,我们总是面临着选择与适配。今天,你的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 实时预览 效果展示

运行到鸿蒙虚拟设备中效果展示
目录
功能代码实现 {#功能代码实现}
加载按钮组件 {#加载按钮组件}
加载按钮组件是本次开发的核心,负责实现按钮点击后显示加载状态的功能。
核心功能
- 点击按钮后显示"加载中…"文字和旋转动画
- 加载过程中按钮不可重复点击
- 加载完成后自动恢复按钮原始状态
- 支持自定义按钮样式和属性
实现代码
import 'package:flutter/material.dart';
class LoadingButton extends StatefulWidget {
final String text;
final Function() onPressed;
final double width;
final double height;
final Color color;
final Color textColor;
final double fontSize;
final BorderRadius borderRadius;
const LoadingButton({
Key? key,
required this.text,
required this.onPressed,
this.width = double.infinity,
this.height = 50.0,
this.color = Colors.blue,
this.textColor = Colors.white,
this.fontSize = 16.0,
this.borderRadius = const BorderRadius.all(Radius.circular(8.0)),
}) : super(key: key);
_LoadingButtonState createState() => _LoadingButtonState();
}
class _LoadingButtonState extends State<LoadingButton> {
bool _isLoading = false;
Future<void> _handlePress() async {
if (_isLoading) return;
setState(() {
_isLoading = true;
});
try {
await widget.onPressed();
} catch (e) {
print('Error: $e');
} finally {
setState(() {
_isLoading = false;
});
}
}
Widget build(BuildContext context) {
return Container(
width: widget.width,
height: widget.height,
child: ElevatedButton(
onPressed: _isLoading ? null : _handlePress,
style: ElevatedButton.styleFrom(
backgroundColor: widget.color,
shape: RoundedRectangleBorder(
borderRadius: widget.borderRadius,
),
),
child: _isLoading
? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
SizedBox(
width: 20.0,
height: 20.0,
child: CircularProgressIndicator(
strokeWidth: 2.0,
valueColor: AlwaysStoppedAnimation<Color>(widget.textColor),
),
),
SizedBox(width: 10.0),
Text(
'加载中...',
style: TextStyle(
color: widget.textColor,
fontSize: widget.fontSize,
),
),
],
)
: Text(
widget.text,
style: TextStyle(
color: widget.textColor,
fontSize: widget.fontSize,
),
),
),
);
}
}
使用方法
LoadingButton(
text: '点击加载',
onPressed: () async {
// 模拟网络请求或其他耗时操作
await Future.delayed(Duration(seconds: 2));
// 操作完成后的处理
},
width: double.infinity,
height: 56,
color: Colors.blue,
textColor: Colors.white,
fontSize: 18,
borderRadius: BorderRadius.circular(12),
)
开发注意事项
- 状态管理:使用StatefulWidget和setState管理按钮的加载状态,确保界面能够实时反映状态变化。
- 异步处理:使用Future和async/await处理异步操作,确保加载状态能够正确管理。
- 错误处理:添加try-catch-finally结构,确保即使操作失败也能恢复按钮状态。
- 防重复点击:在加载过程中禁用按钮,防止用户重复点击导致的问题。
- 样式定制:通过参数化设计,支持自定义按钮的各种样式属性,提高组件的复用性。
主页面集成 {#主页面集成}
主页面负责集成加载按钮组件,展示不同样式的加载按钮,并提供交互效果演示。
核心功能
- 集成多个不同样式的加载按钮
- 显示操作状态提示
- 模拟加载过程
- 提供操作成功反馈
- 添加使用说明
实现代码
import 'package:flutter/material.dart';
import 'loading_button.dart';
class LoadingButtonHome extends StatefulWidget {
const LoadingButtonHome({Key? key}) : super(key: key);
_LoadingButtonHomeState createState() => _LoadingButtonHomeState();
}
class _LoadingButtonHomeState extends State<LoadingButtonHome> {
String _status = '点击按钮开始加载';
Future<void> _simulateLoading() async {
setState(() {
_status = '加载中...';
});
// 模拟网络请求或其他耗时操作
await Future.delayed(Duration(seconds: 2));
setState(() {
_status = '加载完成';
});
// 显示成功提示
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('操作成功!'),
duration: Duration(seconds: 2),
),
);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('按钮加载状态示例'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Padding(
padding: const EdgeInsets.all(20.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
'按钮加载状态演示',
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
),
),
SizedBox(height: 40),
// 状态提示
Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
),
child: Text(
_status,
style: TextStyle(
fontSize: 16,
color: Colors.grey[700],
),
),
),
SizedBox(height: 40),
// 主要加载按钮
LoadingButton(
text: '点击加载',
onPressed: _simulateLoading,
width: double.infinity,
height: 56,
color: Colors.blue,
textColor: Colors.white,
fontSize: 18,
borderRadius: BorderRadius.circular(12),
),
SizedBox(height: 20),
// 不同样式的加载按钮
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: LoadingButton(
text: '红色按钮',
onPressed: _simulateLoading,
width: 150,
height: 48,
color: Colors.red,
textColor: Colors.white,
fontSize: 14,
borderRadius: BorderRadius.circular(8),
),
),
SizedBox(width: 10),
Expanded(
child: LoadingButton(
text: '绿色按钮',
onPressed: _simulateLoading,
width: 150,
height: 48,
color: Colors.green,
textColor: Colors.white,
fontSize: 14,
borderRadius: BorderRadius.circular(8),
),
),
],
),
SizedBox(height: 40),
// 按钮使用说明
Container(
padding: EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(8),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'使用说明:',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
),
),
SizedBox(height: 8),
Text('1. 点击按钮后,按钮会显示加载状态'),
Text('2. 加载过程中,按钮会显示"加载中..."文字和旋转动画'),
Text('3. 加载完成后,按钮会恢复原始状态'),
Text('4. 不同颜色的按钮演示不同风格'),
],
),
),
],
),
),
);
}
}
开发注意事项
- 布局设计:使用Column和Row等布局组件合理组织界面元素,确保界面美观整洁。
- 状态管理:使用StatefulWidget管理页面状态,实时更新操作状态提示。
- 用户反馈:添加SnackBar等反馈机制,提高用户体验。
- 样式统一:保持界面元素的样式统一,确保视觉效果协调。
- 代码组织:合理组织代码结构,提高代码的可读性和可维护性。
应用入口配置 {#应用入口配置}
应用入口文件负责配置应用的主页面,确保加载按钮组件能够在首页直接显示。
核心功能
- 配置应用主题和标题
- 设置主页面为LoadingButtonHome
- 确保应用能够正常启动和运行
实现代码
import 'package:flutter/material.dart';
import 'loading_button/loading_button_home.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> {
Widget build(BuildContext context) {
return LoadingButtonHome();
}
}
开发注意事项
- 导入路径:确保正确导入LoadingButtonHome组件,避免路径错误导致的编译问题。
- 主题配置:合理配置应用主题,确保界面美观。
- 页面设置:正确设置主页面,确保应用启动后能够直接显示加载按钮示例。
- 调试模式:在发布版本中关闭debugShowCheckedModeBanner,提高应用的专业感。
开发中容易遇到的问题 {#开发中容易遇到的问题}
1. 按钮状态管理问题
问题描述
点击按钮后,按钮状态没有正确更新,或者加载完成后没有恢复原始状态。
原因分析
可能的原因包括:
- 没有正确使用setState更新状态
- 异步操作没有正确处理
- 错误处理机制不完善,导致异常后状态没有恢复
解决方案
- 确保在状态变化时调用setState方法
- 使用try-catch-finally结构处理异步操作,确保无论成功还是失败都能恢复按钮状态
- 仔细检查异步操作的实现,确保await关键字的正确使用
2. 布局和样式问题
问题描述
按钮在不同屏幕尺寸下显示不一致,或者样式不符合预期。
原因分析
可能的原因包括:
- 使用了固定尺寸而非相对尺寸
- 没有考虑不同屏幕尺寸的适配
- 样式属性设置不合理
解决方案
- 优先使用相对尺寸(如double.infinity)和自适应布局
- 考虑使用MediaQuery获取屏幕尺寸,进行动态调整
- 合理设置按钮的padding、margin等属性,确保在不同屏幕下的显示效果
3. 导入路径错误
问题描述
编译时出现"找不到文件"或"导入路径错误"的问题。
原因分析
可能的原因包括:
- 文件路径拼写错误
- 文件结构与导入路径不匹配
- 包名或目录结构变更后没有更新导入路径
解决方案
- 仔细检查文件路径和导入语句,确保拼写正确
- 确认文件结构与导入路径一致
- 使用IDE的自动导入功能,减少手动输入错误
4. 异步操作阻塞

问题描述
加载过程中应用界面卡顿,或者按钮点击后没有立即响应。
原因分析
可能的原因包括:
- 在主线程中执行了耗时操作
- 异步操作没有正确实现
- 没有使用适当的异步处理机制
解决方案
- 将耗时操作移至后台线程执行
- 正确使用async/await和Future处理异步操作
- 考虑使用compute或Isolate处理复杂计算
5. 跨平台兼容性问题
问题描述
在Flutter中运行正常,但在鸿蒙平台上出现问题。
原因分析
可能的原因包括:
- 使用了平台特定的API
- 依赖库在鸿蒙平台上不兼容
- 布局或样式在不同平台上的表现差异
解决方案
- 避免使用平台特定的API,优先使用Flutter的跨平台API
- 检查依赖库的兼容性,选择在鸿蒙平台上支持的库
- 测试不同平台上的显示效果,进行必要的适配
总结开发中用到的技术点 {#总结开发中用到的技术点}
1. 状态管理
技术原理:使用StatefulWidget和setState方法管理组件的状态变化,实现界面与数据的同步。
应用场景:适用于需要根据用户交互或其他因素动态改变组件状态的场景,如按钮的加载状态管理。
实现要点:
- 使用StatefulWidget创建有状态的组件
- 在状态变化时调用setState方法通知Flutter框架重建UI
- 合理设计状态变量,避免状态管理混乱
2. 异步编程
技术原理:使用Future、async和await关键字处理异步操作,确保UI在异步操作期间保持响应。
应用场景:适用于需要执行网络请求、文件操作等耗时任务的场景,如模拟加载过程。
实现要点:
- 使用async关键字标记异步函数
- 使用await关键字等待异步操作完成
- 使用try-catch-finally结构处理异步操作中的异常
3. 组件化开发
技术原理:将UI拆分为独立的、可复用的组件,提高代码的可维护性和复用性。
应用场景:适用于构建复杂的用户界面,如将加载按钮封装为独立组件。
实现要点:
- 设计清晰的组件接口,通过参数传递数据和回调函数
- 合理划分组件职责,提高代码的可读性
- 使用参数化设计,支持组件样式的自定义
4. 布局设计
技术原理:使用Flutter的布局组件(如Column、Row、Container等)构建灵活的用户界面。
应用场景:适用于构建各种复杂的用户界面,如主页面的布局设计。
实现要点:
- 合理使用布局组件嵌套,构建层次清晰的界面结构
- 使用padding、margin等属性调整组件间距
- 考虑不同屏幕尺寸的适配,使用相对尺寸而非固定尺寸
5. 用户体验优化
技术原理:通过各种技术手段提高应用的用户体验,增强用户满意度。
应用场景:适用于所有用户交互场景,如按钮的加载状态反馈。
实现要点:
- 提供清晰的视觉反馈,如加载动画、操作成功提示等
- 防止用户误操作,如在加载过程中禁用按钮
- 合理使用SnackBar、Dialog等组件提供操作反馈
6. 跨平台兼容性
技术原理:使用Flutter的跨平台API,确保应用在不同平台上的一致性体验。
应用场景:适用于需要在多个平台上运行的应用,如Flutter for OpenHarmony项目。
实现要点:
- 避免使用平台特定的API,优先使用Flutter的跨平台API
- 测试不同平台上的显示效果,进行必要的适配
- 选择在所有目标平台上兼容的依赖库
通过以上技术点的应用,我们成功实现了一个功能完整、用户体验良好的按钮加载状态组件,并在Flutter for OpenHarmony平台上正常运行。这些技术点不仅适用于本次开发,也是Flutter开发中的通用技术,掌握它们对于构建高质量的Flutter应用至关重要。
更多推荐


所有评论(0)