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 的定位是一个本地汇率换算演示工具。用户输入金额,选择源币种和目标币种,页面会根据内置汇率表立即计算转换结果。

从用户视角看,流程是:

  1. 打开应用,默认金额为 100。
  2. 默认从 USD 转换到 EUR。
  3. 修改金额后结果实时更新。
  4. 修改 From 或 To 币种后结果实时更新。
  5. 点击交换按钮互换两个币种。
  6. 查看底部 USD 基准汇率表。

从工程视角看,流程是:

  1. 使用 TextEditingController 管理金额输入。
  2. 使用字符串状态保存源币种和目标币种。
  3. 使用 Map 保存币种名称和汇率。
  4. 每次输入或币种变化时调用 _convert()
  5. 用 USD 基准汇率进行中转换算。
  6. 用结果卡片展示换算金额。

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'),
    );
  }
}

它完成了:

  1. 设置应用标题为 Currency Converter
  2. 使用绿色作为 Material 3 种子色。
  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 为基准,所以任意币种换算分两步:

  1. 源币种金额先转换成 USD 基准金额。
  2. 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 设备上需要验证:

  1. 交换图标是否正常显示。
  2. 图标点击区域是否足够。
  3. 图标大小是否符合预期。
  4. 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

适合覆盖的行为包括:

  1. 初始金额为 100。
  2. 初始 From 为 USD。
  3. 初始 To 为 EUR。
  4. 修改金额后结果更新。
  5. 点击 swap 后 From/To 交换。
  6. 汇率表显示 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 手动验证流程

手动验证可以按如下顺序进行:

  1. 启动应用,确认默认金额为 100。
  2. 确认默认 From 为 USD,To 为 EUR。
  3. 修改金额,确认结果实时变化。
  4. 切换 From 币种,确认结果变化。
  5. 切换 To 币种,确认结果变化。
  6. 点击交换按钮,确认 From/To 互换。
  7. 删除金额输入,确认结果为 0.00。
  8. 在 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() 触发第二次状态更新。这些边界不影响项目作为入门实战案例使用,但在继续工程化时需要优先接入实时汇率、输入校验和更新时间展示。

如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!


相关资源:

Logo

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

更多推荐