Flutter三方库适配鸿蒙【currency_converter】本地汇率换算器项目完整实战
Flutter三方库适配鸿蒙【currency_converter】本地汇率换算器项目完整实战
前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
currency_converter 是一个离线汇率换算器 Flutter 示例项目。它使用本地静态汇率表,以 USD 作为基准,支持输入金额、选择 From/To 币种、互换币种、实时计算换算结果,并在页面底部展示当前内置汇率表。
需要先明确一点:当前源码中的汇率是 mock exchange rates,不是实时汇率,也没有联网更新能力。因此它适合作为 Flutter 表单、下拉框、Map 数据结构和换算公式的实战案例,不适合作为真实金融交易依据。

图示说明:本文围绕 Flutter 工程中的 currency_converter 项目展开,重点分析本地汇率表、金额输入、币种下拉选择、币种互换、换算公式、结果卡片和 OpenHarmony 适配关注点。
汇率换算器的核心不是一个输入框,而是“金额、源币种、目标币种、汇率表、换算公式和结果展示”之间的状态同步。
本文将基于项目真实源码展开,核心内容包括:
CurrencyConverterApp的应用入口与绿色 Material 3 主题_currencyNames如何保存币种代码和英文名称_exchangeRates如何以 USD 为基准保存本地汇率_convert()如何使用 USD 中转公式完成任意币种换算_swapCurrencies()如何交换 From/To 币种DropdownButtonFormField如何构建币种选择器TextEditingController如何管理金额输入- 当前汇率非实时、输入无效按 0 处理的真实边界
- OpenHarmony 适配时需要验证的输入框、下拉框、滚动和图标能力
一、项目背景与目标
1.1 应用定位
currency_converter 的定位是一个本地汇率换算演示工具。用户输入金额,选择源币种和目标币种,页面会根据内置汇率表立即计算转换结果。
从用户视角看,流程是:
- 打开应用,默认金额为 100。
- 默认从 USD 转换到 EUR。
- 修改金额后结果实时更新。
- 修改 From 或 To 币种后结果实时更新。
- 点击交换按钮互换两个币种。
- 查看底部 USD 基准汇率表。
从工程视角看,流程是:
- 使用
TextEditingController管理金额输入。 - 使用字符串状态保存源币种和目标币种。
- 使用 Map 保存币种名称和汇率。
- 每次输入或币种变化时调用
_convert()。 - 用 USD 基准汇率进行中转换算。
- 用结果卡片展示换算金额。
1.2 当前功能概览
| 功能 | 当前实现 | 技术点 |
|---|---|---|
| 应用入口 | runApp(const CurrencyConverterApp()) |
Flutter 启动流程 |
| 应用主题 | 绿色 Material 3 | ColorScheme.fromSeed |
| 默认金额 | 100 | TextEditingController(text: '100') |
| 默认源币种 | USD | _fromCurrency |
| 默认目标币种 | EUR | _toCurrency |
| 币种名称 | 10 个币种 | _currencyNames |
| 汇率表 | USD 基准静态汇率 | _exchangeRates |
| 换算公式 | 先转 USD,再转目标币种 | _convert() |
| 币种互换 | From/To 交换 | _swapCurrencies() |
| 汇率展示 | 页面底部列表 | Map entries |
1.3 适合学习的能力
这个项目适合学习:
- TextEditingController 输入管理
- DropdownButtonFormField 下拉选择
- Map 数据结构建模
- 本地汇率表换算公式
- 双币种状态同步
- SingleChildScrollView 页面滚动
- 结果卡片和渐变背景
- OpenHarmony 下输入框与下拉菜单适配
二、环境准备与工程结构
2.1 技术栈概览
项目使用 Flutter SDK 自带能力,没有接入汇率 API。
| 类别 | 当前使用 | 说明 |
|---|---|---|
| 开发语言 | Dart | Flutter 应用主语言 |
| UI 框架 | Flutter Material | 页面、输入框、下拉框、卡片 |
| 状态管理 | StatefulWidget + setState |
金额和币种变化 |
| 输入控制 | TextEditingController |
金额输入 |
| 数据结构 | Map<String, double> |
汇率表 |
| 币种选择 | DropdownButtonFormField |
From/To 下拉框 |
| 滚动布局 | SingleChildScrollView |
适配内容高度 |
| 目标适配 | Flutter / OpenHarmony | 输入、下拉、滚动验证 |
2.2 pubspec 关键配置
工程配置如下:
environment:
sdk: ^3.9.2
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
flutter:
uses-material-design: true
当前项目没有网络依赖,因此也没有 HTTP 请求、接口错误、加载状态或汇率更新时间处理。
2.3 主源码结构
核心代码集中在 lib/main.dart:
import 'package:flutter/material.dart';
void main() {
runApp(const CurrencyConverterApp());
}
主要结构如下:
| 结构 | 类型 | 作用 |
|---|---|---|
CurrencyConverterApp |
StatelessWidget |
应用根组件 |
CurrencyConverterHomePage |
StatefulWidget |
汇率换算首页 |
_CurrencyConverterHomePageState |
State |
管理金额、币种和结果 |
2.4 常用运行命令
完成 Flutter 环境准备后,可以执行:
flutter pub get
flutter analyze
flutter test
flutter run
OpenHarmony 环境运行时,需要结合本地 Flutter OpenHarmony 发行版、DevEco Studio、设备连接和签名配置。
三、应用入口与主题配置
3.1 main 函数
应用入口如下:
void main() {
runApp(const CurrencyConverterApp());
}
runApp 会把根组件挂载到 Flutter 渲染树。
3.2 CurrencyConverterApp 根组件
根组件代码如下:
class CurrencyConverterApp extends StatelessWidget {
const CurrencyConverterApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Currency Converter',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
useMaterial3: true,
),
home: const CurrencyConverterHomePage(title: 'Currency Converter'),
);
}
}
它完成了:
- 设置应用标题为
Currency Converter。 - 使用绿色作为 Material 3 种子色。
- 将首页设置为
CurrencyConverterHomePage。
3.3 绿色主题的意义
绿色经常用于金额、收益、财务和确认类视觉反馈。项目中绿色主要用于:
- 根主题 seedColor。
- 结果卡片渐变背景。
- 换算结果文字。
- 页面整体金融工具氛围。
四、页面状态设计
4.1 状态字段
页面状态如下:
final TextEditingController _amountController = TextEditingController(text: '100');
String _fromCurrency = 'USD';
String _toCurrency = 'EUR';
double _result = 0;
字段含义:
| 字段 | 作用 |
|---|---|
_amountController |
控制金额输入框,默认值为 100 |
_fromCurrency |
源币种,默认 USD |
_toCurrency |
目标币种,默认 EUR |
_result |
当前换算结果 |
4.2 初始化流程
页面初始化时调用 _convert():
void initState() {
super.initState();
_convert();
}
这样首次进入页面时,默认 100 USD 到 EUR 的结果会被立即计算出来。
4.3 控制器释放
输入控制器在 dispose() 中释放:
void dispose() {
_amountController.dispose();
super.dispose();
}
这是使用 TextEditingController 的正确做法。
五、币种名称与汇率表
5.1 币种名称表
币种名称表如下:
final Map<String, String> _currencyNames = {
'USD': 'US Dollar',
'EUR': 'Euro',
'GBP': 'British Pound',
'JPY': 'Japanese Yen',
'CNY': 'Chinese Yuan',
'KRW': 'Korean Won',
'INR': 'Indian Rupee',
'AUD': 'Australian Dollar',
'CAD': 'Canadian Dollar',
'CHF': 'Swiss Franc',
};
它用于下拉框和结果卡片的名称展示。
5.2 汇率表
汇率表以 USD 为基准:
final Map<String, double> _exchangeRates = {
'USD': 1.0,
'EUR': 0.92,
'GBP': 0.79,
'JPY': 149.50,
'CNY': 7.24,
'KRW': 1330.00,
'INR': 83.12,
'AUD': 1.53,
'CAD': 1.36,
'CHF': 0.88,
};
5.3 支持币种列表
| 代码 | 名称 |
|---|---|
| USD | US Dollar |
| EUR | Euro |
| GBP | British Pound |
| JPY | Japanese Yen |
| CNY | Chinese Yuan |
| KRW | Korean Won |
| INR | Indian Rupee |
| AUD | Australian Dollar |
| CAD | Canadian Dollar |
| CHF | Swiss Franc |
5.4 静态汇率边界
当前汇率表写死在源码中,没有更新时间,也不代表当前真实市场汇率。它适合用于演示换算公式和 UI 交互。
如果要做真实汇率工具,需要接入汇率 API,并展示数据来源和更新时间。
六、换算公式实现
6.1 _convert 方法
换算逻辑如下:
void _convert() {
final amount = double.tryParse(_amountController.text) ?? 0;
final fromRate = _exchangeRates[_fromCurrency] ?? 1;
final toRate = _exchangeRates[_toCurrency] ?? 1;
setState(() {
_result = (amount / fromRate) * toRate;
});
}
6.2 公式含义
因为汇率表以 USD 为基准,所以任意币种换算分两步:
- 源币种金额先转换成 USD 基准金额。
- USD 基准金额再转换成目标币种金额。
公式如下:
result = (amount / fromRate) * toRate
6.3 示例计算
如果 100 EUR 转 CNY:
amount = 100
fromRate = 0.92
toRate = 7.24
result = (100 / 0.92) * 7.24
这表示先把 EUR 转成 USD 基准,再转成 CNY。
6.4 无效输入边界
金额解析使用:
double.tryParse(_amountController.text) ?? 0
如果输入不是合法数字,amount 会变成 0,页面结果也会显示 0.00。当前没有错误提示。
七、金额输入框
7.1 TextField 配置
金额输入框如下:
TextField(
controller: _amountController,
keyboardType: TextInputType.number,
style: const TextStyle(fontSize: 32),
decoration: InputDecoration(
labelText: 'Amount',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
filled: true,
),
onChanged: (_) => _convert(),
)
7.2 实时计算
每次输入变化都会调用:
onChanged: (_) => _convert()
这让换算结果实时刷新。
7.3 数字键盘边界
当前使用:
keyboardType: TextInputType.number
在部分平台上,这可能不方便输入小数点。更适合金额输入的配置是:
keyboardType: const TextInputType.numberWithOptions(decimal: true)
这样可以更明确地允许小数金额。
7.4 负数和空值边界
当前源码没有限制负数,也没有为空输入展示错误。负数输入会得到负数结果,空输入会按 0 处理。
如果是面向真实用户的工具,应该增加输入校验和错误提示。
八、币种选择器
8.1 _buildCurrencySelector 方法
币种选择器复用方法如下:
Widget _buildCurrencySelector(String label, String currency) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(label, style: TextStyle(color: Colors.grey.shade600)),
const SizedBox(height: 8),
DropdownButtonFormField<String>(...),
const SizedBox(height: 4),
Text(_currencyNames[currency] ?? ''),
],
),
),
);
}
8.2 From 与 To 复用
页面中调用两次:
_buildCurrencySelector('From', _fromCurrency)
_buildCurrencySelector('To', _toCurrency)
同一个方法根据 label 判断更新哪个状态。
8.3 下拉框选项
下拉项由 _currencyNames.entries 生成:
items: _currencyNames.entries.map((entry) {
return DropdownMenuItem(
value: entry.key,
child: Text(entry.key),
);
}).toList()
显示的是币种代码,如 USD、EUR、CNY。
8.4 onChanged 逻辑
选择币种后:
onChanged: (value) {
if (value != null) {
setState(() {
if (label == 'From') {
_fromCurrency = value;
} else {
_toCurrency = value;
}
});
_convert();
}
}
先更新币种,再调用 _convert() 重新计算。
九、币种互换逻辑
9.1 _swapCurrencies 方法
互换逻辑如下:
void _swapCurrencies() {
setState(() {
final temp = _fromCurrency;
_fromCurrency = _toCurrency;
_toCurrency = temp;
});
_convert();
}
它会交换 From 和 To,然后重新计算结果。
9.2 交换按钮
页面中间的按钮如下:
IconButton(
onPressed: _swapCurrencies,
icon: const Icon(Icons.swap_horiz),
iconSize: 32,
)
用户点击后即可反向换算。
9.3 双 setState 边界
_swapCurrencies() 中先 setState 交换币种,随后 _convert() 内部又调用一次 setState 更新结果。
这种写法能正常工作,但可以合并为一次状态更新:
setState(() {
final temp = _fromCurrency;
_fromCurrency = _toCurrency;
_toCurrency = temp;
final amount = double.tryParse(_amountController.text) ?? 0;
_result = (amount / fromRate) * toRate;
});
当前实现更直观,优化空间主要在状态更新次数。
十、结果卡片与汇率表展示
10.1 结果卡片
转换结果显示在带渐变的 Card 中:
Card(
elevation: 8,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
child: Container(
padding: const EdgeInsets.all(24),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(20),
gradient: LinearGradient(
colors: [Colors.green.shade100, Colors.green.shade50],
),
),
),
)
10.2 结果格式
结果保留两位小数:
_result.toStringAsFixed(2)
这符合金额展示的常见习惯。
10.3 目标币种名称
结果下方显示:
'$_toCurrency - ${_currencyNames[_toCurrency]}'
例如:
EUR - Euro
10.4 汇率表展示
底部展示 USD 基准汇率:
..._exchangeRates.entries.map((entry) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(entry.key),
Text(entry.value.toStringAsFixed(4)),
],
);
})
这样用户能看到当前本地表中的换算数据。
十一、页面布局与滚动
11.1 SingleChildScrollView
页面主体使用:
SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// content
],
),
)
这能避免小屏幕或键盘弹出时内容被截断。
11.2 From/To 横向布局
两个选择器和交换按钮放在 Row 中:
Row(
children: [
Expanded(child: _buildCurrencySelector('From', _fromCurrency)),
IconButton(...),
Expanded(child: _buildCurrencySelector('To', _toCurrency)),
],
)
Expanded 让 From 和 To 平分剩余空间。
11.3 小屏幕边界
两个 Card 加一个 IconButton 在很窄屏幕上可能拥挤。当前页面使用英文币种代码,空间相对可控;如果显示完整币种名称,可能需要改为上下布局。
11.4 键盘适配
金额输入会弹出键盘,SingleChildScrollView 能帮助用户滚动查看结果卡片和汇率表。
十二、OpenHarmony 适配要点
12.1 适配关注范围
currency_converter 没有平台插件,重点验证 Flutter 输入、下拉框、滚动和图标。
| 适配项 | 涉及源码 | 验证重点 |
|---|---|---|
| MaterialApp | 根组件 | 应用启动和主题 |
| TextField | 金额输入 | 数字键盘、小数输入 |
| DropdownButtonFormField | 币种选择 | 弹出菜单和选择 |
| IconButton | 币种互换 | 点击和图标 |
| SingleChildScrollView | 滚动布局 | 小屏和键盘 |
| Card | 结果和汇率表 | 圆角、阴影 |
| LinearGradient | 结果卡片背景 | 渐变渲染 |
| Text | 金额和汇率 | 小数格式、等宽字体 |
12.2 图标资源验证
项目使用了:
Icons.swap_horiz
OpenHarmony 设备上需要验证:
- 交换图标是否正常显示。
- 图标点击区域是否足够。
- 图标大小是否符合预期。
- Material Icons 字体是否随包体加载。
12.3 输入体验验证
金额输入是核心入口:
TextField(
keyboardType: TextInputType.number,
onChanged: (_) => _convert(),
)
需要验证:
- 点击输入框是否弹出数字键盘。
- 小数输入是否方便。
- 输入非法字符时结果是否稳定。
- 删除内容后是否显示 0.00。
- 结果是否实时刷新。
12.4 下拉菜单验证
币种选择依赖:
DropdownButtonFormField<String>
需要验证:
- 下拉菜单是否能正常展开。
- 币种选择后是否更新。
- From 和 To 是否互不影响。
- 切换后是否立即换算。
十三、测试与验证
13.1 静态分析
建议执行:
flutter analyze
重点关注:
TextEditingController是否释放。- 汇率表和币种名称是否一致。
_convert()是否处理非法输入。- 下拉框 value 是否存在于 items 中。
13.2 组件测试方向
可以执行:
flutter test
适合覆盖的行为包括:
- 初始金额为 100。
- 初始 From 为 USD。
- 初始 To 为 EUR。
- 修改金额后结果更新。
- 点击 swap 后 From/To 交换。
- 汇率表显示 USD、EUR、CNY 等币种。
13.3 示例测试代码
下面是一段基础页面测试思路:
testWidgets('shows currency converter initial state', (tester) async {
await tester.pumpWidget(const CurrencyConverterApp());
expect(find.text('Currency Converter'), findsOneWidget);
expect(find.text('Amount'), findsOneWidget);
expect(find.text('Converted Amount'), findsOneWidget);
expect(find.text('Exchange Rates (Base: USD)'), findsOneWidget);
});
这能确认页面基础结构正常。
13.4 换算测试思路
可以验证默认结果:
testWidgets('converts default USD to EUR', (tester) async {
await tester.pumpWidget(const CurrencyConverterApp());
expect(find.text('92.00'), findsOneWidget);
});
因为默认金额是 100,默认汇率中 EUR 是 0.92,因此结果为 92.00。
13.5 手动验证流程
手动验证可以按如下顺序进行:
- 启动应用,确认默认金额为 100。
- 确认默认 From 为 USD,To 为 EUR。
- 修改金额,确认结果实时变化。
- 切换 From 币种,确认结果变化。
- 切换 To 币种,确认结果变化。
- 点击交换按钮,确认 From/To 互换。
- 删除金额输入,确认结果为 0.00。
- 在 OpenHarmony 设备上验证数字键盘和下拉菜单。
十四、常见问题与优化建议
14.1 为什么汇率不是实时的
因为汇率表写在源码中:
final Map<String, double> _exchangeRates = {
'USD': 1.0,
'EUR': 0.92,
}
没有网络请求,也没有更新时间。它是本地演示数据。
14.2 为什么输入非法内容结果变成 0
因为 _convert() 使用:
final amount = double.tryParse(_amountController.text) ?? 0;
解析失败时 amount 变成 0。
14.3 为什么没有货币符号
当前结果只显示数值和币种代码:
_result.toStringAsFixed(2)
没有 $、€、¥ 等符号,也没有本地化格式。
14.4 为什么 From/To 可以选择相同币种
当前没有禁止相同币种。如果 From 和 To 相同,换算结果等于输入金额,这是合理结果。
14.5 是否适合真实交易
不适合。当前项目没有实时汇率、数据来源、更新时间、误差说明和金融级校验,只适合作为 Flutter 本地换算示例。
十五、工程扩展方向
15.1 接入实时汇率 API
可以引入网络请求,获取实时汇率:
Future<Map<String, double>> fetchRates() async {
// request exchange rate API
return {};
}
接入后需要增加加载状态、错误状态和更新时间展示。
15.2 增加输入校验
可以对金额做校验:
String? validateAmount(String text) {
final value = double.tryParse(text);
if (value == null) return 'Please enter a valid number';
if (value < 0) return 'Amount must be greater than or equal to 0';
return null;
}
这样用户能知道输入为什么不可用。
15.3 增加货币格式化
可以为不同币种显示符号:
final symbols = {
'USD': r'$',
'EUR': '€',
'CNY': '¥',
};
展示结果时组合符号和数值。
15.4 增加收藏币种
可以保存常用币种对:
class CurrencyPair {
const CurrencyPair({
required this.from,
required this.to,
});
final String from;
final String to;
}
用户可以快速切换常用换算。
15.5 增加更新时间显示
真实汇率工具应该展示更新时间:
DateTime? _lastUpdatedAt;
页面可以显示:
Text('Last updated: $_lastUpdatedAt')
这能让用户理解数据时效。
十六、完整核心代码回顾
16.1 应用入口
void main() {
runApp(const CurrencyConverterApp());
}
入口负责启动根组件。
16.2 根组件
class CurrencyConverterApp extends StatelessWidget {
const CurrencyConverterApp({super.key});
Widget build(BuildContext context) {
return MaterialApp(
title: 'Currency Converter',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.green),
useMaterial3: true,
),
home: const CurrencyConverterHomePage(title: 'Currency Converter'),
);
}
}
根组件负责主题和首页绑定。
16.3 汇率表
final Map<String, double> _exchangeRates = {
'USD': 1.0,
'EUR': 0.92,
'GBP': 0.79,
'JPY': 149.50,
};
汇率以 USD 为基准。
16.4 换算公式
void _convert() {
final amount = double.tryParse(_amountController.text) ?? 0;
final fromRate = _exchangeRates[_fromCurrency] ?? 1;
final toRate = _exchangeRates[_toCurrency] ?? 1;
setState(() {
_result = (amount / fromRate) * toRate;
});
}
这是整个项目的核心逻辑。
16.5 币种互换
void _swapCurrencies() {
setState(() {
final temp = _fromCurrency;
_fromCurrency = _toCurrency;
_toCurrency = temp;
});
_convert();
}
互换后会重新计算结果。
16.6 下拉选择
DropdownButtonFormField<String>(
value: currency,
items: _currencyNames.entries.map((entry) {
return DropdownMenuItem(
value: entry.key,
child: Text(entry.key),
);
}).toList(),
onChanged: (value) {
if (value != null) {
// update currency
}
},
)
下拉选择器负责 From 和 To 币种切换。
总结
currency_converter 用 Flutter 实现了一个离线汇率换算器:它通过 _amountController 管理金额输入,通过 _fromCurrency 和 _toCurrency 管理源币种与目标币种,通过 _currencyNames 展示币种名称,通过 _exchangeRates 保存 USD 基准静态汇率,并用 (amount / fromRate) * toRate 完成任意币种之间的换算。
从 OpenHarmony 适配角度看,这个项目覆盖了 Material 主题、TextField、DropdownButtonFormField、IconButton、SingleChildScrollView、Card、LinearGradient 和 Map 列表渲染等基础能力,很适合验证 Flutter 表单类工具页面在 OpenHarmony 上的表现。
当前源码也有几个真实边界:汇率是本地静态 mock 数据,不是实时市场汇率;无效金额输入会按 0 处理;负数金额没有限制;金额没有货币符号和本地化格式;From/To 状态变化后会再次调用 _convert() 触发第二次状态更新。这些边界不影响项目作为入门实战案例使用,但在继续工程化时需要优先接入实时汇率、输入校验和更新时间展示。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!
相关资源:
- OpenHarmony 官网:https://www.openharmony.cn
- OpenHarmony 文档:https://docs.openharmony.cn
- 开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
- Flutter 官网:https://flutter.dev
- Flutter 文档:https://docs.flutter.dev
更多推荐


所有评论(0)