ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

HarmonyOS 应用开发实战|货币转换(Currency Converter)— Flex 布局与数据驱动精讲

2026/8/7 11:28:06 拓冰建站 浏览量
HarmonyOS 应用开发实战|货币转换(Currency Converter)— Flex 布局与数据驱动精讲 一、应用概述货币转换Currency Converter是一款实用的汇率换算工具支持人民币CNY、美元USD、欧元EUR、日元JPY、英镑GBP和韩元KRW六大主流货币之间的实时换算。用户只需输入金额并选择源货币与目标货币即可获得精确的换算结果同时支持一键互换货币对和快速切换源货币。应用基于 HarmonyOS 的 ArkTS 语言和声明式 UI 框架构建完美展示了 Flex 弹性布局、Record 字典数据结构、双向数据流、以及 UI 状态驱动的货币选择等核心技术的综合运用。对于鸿蒙开发者而言货币转换是一个典型的数据驱动应用案例——它让你在约90行代码中同时掌握字典查找、双向绑定、弹性换行布局三大关键能力这些能力广泛适用于汇率、计算器、配置面板等数据展示与操作场景。1.1 功能特性六大货币支持人民币、美元、欧元、日元、英镑、韩元实时汇率换算输入金额立即转换无需点击确认按钮货币符号显示每种货币使用其标准符号¥、$、€、£、₩一键互换功能点击圆形按钮快速交换源货币和目标货币汇率信息展示显示当前使用的汇率值货币快速选择通过 Flex 布局的可点击标签切换源货币货币名称展示每种货币显示其中文名称提升可读性1.2 适用场景出国旅游前的汇率查询跨境电商购物比价外汇投资参考国际贸易成本计算财务报告货币换算二、技术架构2.1 整体架构概览┌─────────────────────────────────────────┐ │ 数据层 │ │ RATES (Recordstring, Record) 汇率表 │ │ CURRENCIES (string[]) 货币列表 │ │ SYMBOLS / NAMES 元数据字典 │ ├─────────────────────────────────────────┤ │ 状态管理层 │ │ State fromCurrency / toCurrency / │ │ amount / result │ ├─────────────────────────────────────────┤ │ 业务逻辑层 │ │ convert() 汇率换算 get rate() 汇率查询 │ ├─────────────────────────────────────────┤ │ UI 表示层 │ │ 输入面板 / 互换按钮 / 结果面板 / │ │ 货币选择区 │ └─────────────────────────────────────────┘2.2 核心数据结构设计汇率数据使用嵌套的Record类型存储const RATES: Recordstring, Recordstring, number { CNY: { USD: 0.14, EUR: 0.13, JPY: 21.5, GBP: 0.11, KRW: 190, CNY: 1 }, USD: { CNY: 7.15, EUR: 0.92, JPY: 153, GBP: 0.79, KRW: 1360, USD: 1 }, EUR: { CNY: 7.78, USD: 1.09, JPY: 166, GBP: 0.86, KRW: 1480, EUR: 1 }, JPY: { CNY: 0.047, USD: 0.0065, EUR: 0.0060, GBP: 0.0052, KRW: 8.9, JPY: 1 }, };RecordK, V是 ArkTS 中的字典类型等价于 TypeScript 的RecordK, V。这里使用了两层嵌套第一层键为源货币代码第二层键为目标货币代码值为汇率直接汇率即1单位源货币可兑换的目标货币数这种设计实现了O(1) 时间复杂度的汇率查找比数组遍历或 switch-case 语句高效得多。辅助元数据同样使用Record存储const SYMBOLS: Recordstring, string { CNY: ¥, USD: $, EUR: €, JPY: ¥, GBP: £, KRW: ₩ }; const NAMES: Recordstring, string { CNY: 人民币, USD: 美元, EUR: 欧元, JPY: 日元, GBP: 英镑, KRW: 韩元 };三、核心代码深度分析3.1 完整的页面源码import { router } from kit.ArkUI; const RATES: Recordstring, Recordstring, number { CNY: { USD: 0.14, EUR: 0.13, JPY: 21.5, GBP: 0.11, KRW: 190, CNY: 1 }, USD: { CNY: 7.15, EUR: 0.92, JPY: 153, GBP: 0.79, KRW: 1360, USD: 1 }, EUR: { CNY: 7.78, USD: 1.09, JPY: 166, GBP: 0.86, KRW: 1480, EUR: 1 }, JPY: { CNY: 0.047, USD: 0.0065, EUR: 0.0060, GBP: 0.0052, KRW: 8.9, JPY: 1 }, }; const CURRENCIES: string[] [CNY, USD, EUR, JPY, GBP, KRW]; const SYMBOLS: Recordstring, string { CNY: ¥, USD: $, EUR: €, JPY: ¥, GBP: £, KRW: ₩ }; const NAMES: Recordstring, string { CNY: 人民币, USD: 美元, EUR: 欧元, JPY: 日元, GBP: 英镑, KRW: 韩元 }; Entry Component struct index22 { State fromCurrency: string CNY; State toCurrency: string USD; State amount: string 100; State result: string 14.00; convert(): void { const val parseFloat(this.amount); if (isNaN(val) || val 0) { this.result 无效; return; } const rate RATES[this.fromCurrency]?.[this.toCurrency]; if (rate undefined) { this.result 无汇率; return; } this.result (val * rate).toFixed(2); } get rate(): string { const r RATES[this.fromCurrency]?.[this.toCurrency]; return (r ! undefined ? r : 0).toFixed(4); } build() { Column() { Row() { Text(‹ 返回).fontSize(16).fontColor(#0077FF).onClick(() router.back()) Text( 货币转换).fontSize(18).fontWeight(FontWeight.Bold) Blank() }.width(92%).padding({ top: 16, bottom: 8 }) Column() { // 源货币输入面板 Column() { Row() { Text(SYMBOLS[this.fromCurrency]).fontSize(24).fontWeight(FontWeight.Bold).width(36) TextInput({ text: this.amount }) .layoutWeight(1).height(48).backgroundColor(#fff).borderRadius(8) .type(InputType.Number).padding({ left: 12 }).fontSize(20) .onChange((v: string) { this.amount v; this.convert() }) } Text(NAMES[this.fromCurrency]).fontSize(13).fontColor(#888) }.width(92%).padding(12).backgroundColor(#fff).borderRadius(12) // 互换按钮 Button() { Text(⇅ 互换).fontSize(20).fontColor(#fff) }.width(56).height(56).borderRadius(28).backgroundColor(#0077FF).margin(8) .onClick(() { const t this.fromCurrency; this.fromCurrency this.toCurrency; this.toCurrency t; this.convert(); }) // 目标货币结果面板 Column() { Row() { Text(SYMBOLS[this.toCurrency]).fontSize(24).fontWeight(FontWeight.Bold).width(36) Text(this.result) .layoutWeight(1).height(48).fontSize(24).fontWeight(FontWeight.Bold) .fontColor(#F44336).padding({ left: 12 }) } Text(NAMES[this.toCurrency]).fontSize(13).fontColor(#888) }.width(92%).padding(12).backgroundColor(#fff).borderRadius(12) // 汇率展示 Text(汇率: 1 this.fromCurrency this.rate this.toCurrency) .fontSize(13).fontColor(#888).margin({ top: 12 }) // 货币快速选择区 Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Center }) { ForEach(CURRENCIES, (c: string) { Text(NAMES[c]) .fontSize(14).padding(8) .backgroundColor(c this.fromCurrency ? #0077FF : #eee) .fontColor(c this.fromCurrency ? #fff : #333) .borderRadius(16).margin(4) .onClick(() { this.fromCurrency c; this.convert() }) }) }.width(92%).margin({ top: 16 }) } .layoutWeight(1).alignItems(HorizontalAlign.Center).padding({ top: 20 }) } .width(100%).height(100%).backgroundColor(#f8f8f8) } }3.2 汇率查找机制const rate RATES[this.fromCurrency]?.[this.toCurrency]; if (rate undefined) { this.result 无汇率; return; } this.result (val * rate).toFixed(2);这里使用了可选链操作符?.这是 ArkTS/TypeScript 提供的一种安全访问嵌套对象属性的语法RATES[this.fromCurrency]获取源货币的汇率字典?.[this.toCurrency]如果源货币字典存在则继续查找目标货币汇率如果任一环节不存在rate的值为undefined这种写法避免了多层if判断和可能的Cannot read properties of undefined运行时错误是现代 ArkTS 开发中推荐的安全访问模式。3.3 互换功能的实现.onClick(() { const t this.fromCurrency; this.fromCurrency this.toCurrency; this.toCurrency t; this.convert(); })互换操作使用了一个临时变量t来暂存fromCurrency的值然后完成两个变量的交换。这种三角交换是编程中最基础的算法模式之一。交换后立即调用convert()重新计算结果确保 UI 显示与最新状态同步。3.4 Flex 弹性换行布局Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.Center }) { ForEach(CURRENCIES, (c: string) { Text(NAMES[c]) .fontSize(14).padding(8) .backgroundColor(c this.fromCurrency ? #0077FF : #eee) .fontColor(c this.fromCurrency ? #fff : #333) .borderRadius(16).margin(4) .onClick(() { this.fromCurrency c; this.convert() }) }) }.width(92%).margin({ top: 16 })Flex组件是 HarmonyOS 中实现弹性布局的利器支持主轴和交叉轴两个维度的灵活排列。本应用中使用了以下关键参数wrap: FlexWrap.Wrap允许子项自动换行当一行排不下时折到下一行justifyContent: FlexAlign.Center主轴方向居中对齐如果不使用FlexWrap.Wrap当货币标签较多时它们会被压缩在一行内显示不全。启用换行后标签会在多行中自然分布适配不同屏幕宽度。每个货币标签同样使用了状态驱动的样式切换.backgroundColor(c this.fromCurrency ? #0077FF : #eee) .fontColor(c this.fromCurrency ? #fff : #333)当前选中的源货币显示为蓝色背景白色文字其他货币显示为灰色背景深色文字。点击任一货币标签立即切换源货币并触发换算。3.5 输入即计算的实时响应TextInput({ text: this.amount }) // ... .onChange((v: string) { this.amount v; this.convert() })与需要点击按钮才计算的模式不同本应用在onChange回调中直接调用convert()方法实现了输入即计算的实时响应体验。用户每输入一个数字换算结果就立即更新这种交互模式在计算器类应用中尤为重要。四、HarmonyOS 特性深度解析4.1 Record 字典类型与类型安全RecordK, V是 ArkTS 中用于定义字典键值对的类型工具。它的类型参数K键的类型通常为string或numberV值的类型const RATES: Recordstring, Recordstring, number { ... };这表示RATES是一个字典其键为字符串货币代码值为另一个字典Recordstring, number——目标货币代码到汇率的映射。使用Record而非普通对象的好处类型约束编译时检查键和值的类型是否正确可读性明确表达了这是一个字典的语义安全性访问不存在的键时返回undefined而非抛出异常4.2 TextInput 与 State 的双向数据绑定TextInput({ text: this.amount }) .onChange((v: string) { this.amount v; this.convert() })在 ArkTS 中TextInput的text参数是单向绑定的——组件显示State的值但用户输入的变化需要通过onChange回调手动同步回State变量。这种模式实现了外观上的双向绑定用户输入 → onChange → this.amount v → UI 重新渲染 → TextInput 显示最新值如果需要真正的双向绑定即修改State变量后TextInput 自动更新其显示内容则必须确保text参数始终引用最新的State值。本应用中正是这样做的因此当convert()方法没有修改amount时TextInput 的内容不会意外变化。4.3 布局权重的灵活运用TextInput({ text: this.amount }) .layoutWeight(1) // 占据剩余空间 .height(48)layoutWeight(1)是 ArkTS 中实现弹性宽度的关键属性。在Row布局中当货币符号Text固定宽度为36pxTextInput通过layoutWeight(1)占据剩余的所有空间。这种设计确保货币符号始终可见且不压缩输入框自适应屏幕宽度在不同屏幕尺寸下都能完美适配4.4 Button 的内容插槽Button() { Text(⇅ 互换).fontSize(20).fontColor(#fff) }HarmonyOS 的Button组件支持内容插槽Content Slot即可以在Button的尾随闭包中放入任意 UI 组件作为按钮的内容。这与传统移动开发中按钮只能显示文本的模式不同ArkTS 的按钮可以是文字、图标、甚至复杂布局的组合。五、UI/UX 设计模式5.1 视觉层级设计应用的视觉层级遵循输入 → 操作 → 输出的自然流程┌─────────────────────────────────┐ │ ① 源货币输入面板白色卡片 │ │ 显示货币符号和名称 │ ├─────────────────────────────────┤ │ ② 互换按钮圆形蓝色按钮 │ ├─────────────────────────────────┤ │ ③ 目标货币结果面板白色卡片 │ │ 结果显示为红色大字体 │ ├─────────────────────────────────┤ │ ④ 汇率信息灰色小字 │ ├─────────────────────────────────┤ │ ⑤ 货币选择区Flex 标签 │ └─────────────────────────────────┘这种从上到下的视觉引导让用户自然完成输入→查看结果的任务流程。5.2 输入与结果面板对比源货币面板和目标货币面板采用了对称设计元素源货币面板目标货币面板背景白色卡片白色卡片符号24px 粗体24px 粗体内容TextInput 可编辑Text 只读显示结果颜色默认黑色红色强调货币名称灰色小字灰色小字两个面板的高度、内边距、圆角都保持一致形成视觉上的对仗感。唯一的区别在于——源货币面板是可编辑的输入框目标货币面板是只读的结果展示——这种同中有异的设计清晰传达了各自的功能角色。5.3 互换按钮的圆形设计Button() { Text(⇅ 互换).fontSize(20).fontColor(#fff) }.width(56).height(56).borderRadius(28).backgroundColor(#0077FF).margin(8)互换按钮被设计为圆形borderRadius(28)等于宽度56的一半这是一种符合 Material Design 规范的 FABFloating Action Button浮动操作按钮风格。圆形按钮在两个矩形面板之间形成视觉节奏的变化同时⇅符号直观传达了上下互换的操作含义。六、最佳实践与经验总结6.1 汇率数据管理本应用使用硬编码的模拟汇率数据适用于演示和学习场景。在生产环境中建议使用远程 API通过http模块从第三方汇率服务获取实时汇率本地缓存将获取的汇率缓存到Preferences中减少网络请求定时更新使用setInterval定期刷新汇率数据离线容错当网络不可用时使用缓存或最后已知的汇率6.2 货币代码标准化使用 ISO 4217 标准的三字母货币代码CNY、USD、EUR 等作为货币标识这是国际金融领域的通行做法。好处包括通用性全球通用的标准代码可扩展性易于添加新货币兼容性与外部 API 的数据格式兼容6.3 输入防抖考量在实时换算场景中如果未来接入网络 API 获取实时汇率频繁的onChange触发可能导致大量网络请求。此时应考虑输入防抖Debounce机制// 防抖示例非本应用代码 let debounceTimer: number -1; onChange((v: string) { this.amount v; clearTimeout(debounceTimer); debounceTimer setTimeout(() this.convert(), 300); });不过在本应用中换算是纯本地计算性能开销极小因此无需防抖。6.4 可选链的安全访问const rate RATES[this.fromCurrency]?.[this.toCurrency];这种写法等价于const fromRates RATES[this.fromCurrency]; const rate fromRates ! undefined ? fromRates[this.toCurrency] : undefined;可选链操作符让代码更简洁、更安全是 ArkTS 开发中推荐使用的现代语法特性。6.5 国际化考虑本应用已经包含了中文货币名称。如果要支持多语言可以将NAMES设计为按语言分组的嵌套字典const NAMES: Recordstring, Recordstring, string { zh: { CNY: 人民币, ... }, en: { CNY: Chinese Yuan, ... }, };七、总结货币转换应用在约90行代码中完美展示了 ArkTS 声明式 UI 开发的多个核心概念Record 字典数据结构O(1) 时间复杂度的高效数据查询Flex 弹性换行布局自适应屏幕宽度的标签排列State 状态驱动自动触发的 UI 渲染更新实时计算反馈输入即计算的流畅交互体验可选链安全访问优雅的嵌套数据访问方式状态驱动的样式切换视觉反馈与功能状态绑定这个应用不仅是一个实用的汇率换算工具更是一个数据驱动 UI 开发的典范案例。它清晰地展示了如何将数据结构、业务逻辑和 UI 表示层有机地组织在一起构建出既高效又美观的应用程序。对于希望深入学习 ArkTS 数据管理和布局技术的开发者来说货币转换应用提供了一个绝佳的参考范本。