【鸿蒙 NEXT ArkUI 实战 012|API20】从零手写汇率转换器,全源码可直接粘贴运行,ArkTS 语法避坑详解
一、项目概述
1.1 项目开发背景
在跨境出行、海淘购物、境外理财常态化的当下,汇率换算成为高频刚需工具。市面上多数汇率工具依赖第三方 App,缺少轻量化原生桌面小工具;本项目依托鸿蒙原生能力开发轻量化汇率转换应用,前期采用本地模拟汇率数据快速落地业务逻辑,后续可无缝对接公开汇率 API 替换静态数据,适配手机、平板多端显示,是入门 ArkUI 数据绑定、表单交互、数值运算的经典实战项目。
1.2 应用落地场景
表格
使用场景 具体用途
出境旅游 实时折算当地物价,规划旅行消费预算
跨境网购 外币商品快速换算人民币,比价决策
个人外汇理财 快速查看主流币种兑美元基准汇率
外贸商务 商务报价即时币种换算,提升办公效率
多币种资产管理 外币存款、海外收入简易折算统计
1.3 产品功能特性
全币种适配:内置 USD/CNY/EUR/JPY/GBP/KRW/HKD/TWD 八大主流币种,覆盖日常使用需求;
即时联动换算:输入金额实时自动计算结果,无需额外点击转换按钮;
一键币种互换:中间交换按钮快速调换源货币、目标货币,自动刷新换算;
汇率明细展示:结果区展示单币种兑另一币种实时基准汇率;
常用汇率看板:首页列表固定展示四大热门币种基准汇率,快速查阅;
异常输入拦截:空值、负数、非法字符自动捕获,友好文字提示错误。
1.4 UI 设计与最终效果图
整体采用金融绿色主题(#059669),契合财富、金融视觉调性,页面自上而下分为 5 大模块:顶部标题区→金额输入区→币种选择区→换算结果区→常用汇率列表区,卡片圆角 + 白色面板分层设计,符合 HarmonyOS Design 设计规范。
plaintext
┌─────────────────────────────────┐
│ 【汇率转换】标题 │
├─────────────────────────────────┤
│ 输入金额|输入框面板 │
├─────────────────────────────────┤
│ 【从】[币种下拉] ⇄ 【到】[币种下拉] │
│ 币种中文名 币种中文名 │
├─────────────────────────────────┤
│ 换算结果(大号绿色字体) │
│ 1 USD = 7.24 CNY汇率说明 │
├─────────────────────────────────┤
│ 常用汇率|币种名称|基准价格列表 │
└─────────────────────────────────┘
1.5 项目技术栈
系统:HarmonyOS NEXT(纯原生,不兼容 OpenHarmony 标准版)
API:API 20+
语言:ArkTS(兼容 TS 语法,强类型约束)
UI:ArkUI 声明式布局(Column/Row/Select/TextInput/List/ForEach)
核心语法:Record 键值映射、数组高阶方法、状态驱动渲染、条件渲染
二、核心知识点精讲(10 个高频鸿蒙开发要点)
本章为全文重点,每个知识点附原理 + 项目落地代码 + 使用优势,可直接复用至单位换算、计算器类项目
2.1 Record 键值映射类型(项目核心数据载体)
Record 为 TS/ArkTS 内置工具泛型,统一管理同类型键值数据,本项目用三组 Record 分别存储汇率、货币符号、币种中文名,集中式维护便于后期新增币种、修改汇率。
// 基准:以USD美元=1为锚点,存储各币种兑美元汇率
private readonly rates: Record<string, number> = {
'USD': 1.0, 'CNY': 7.24, 'EUR': 0.92, 'JPY': 149.50,
'GBP': 0.79, 'KRW': 1320.0, 'HKD': 7.83, 'TWD': 31.5
}
// 币种符号映射
private readonly symbols: Record<string, string> = {
'USD': '$','CNY': '¥','EUR': '€','JPY': '¥','GBP': '£','KRW': '₩','HKD': 'HK$','TWD': 'NT$'
}
// 币种中文名称
private readonly names: Record<string, string> = {
'USD':'美元','CNY':'人民币','EUR':'欧元','JPY':'日元','GBP':'英镑','KRW':'韩元','HKD':'港币','TWD':'新台币'
}
Record 四大优势
类型强校验:编译阶段约束键、值数据类型,写错键名 IDE 直接报错,规避运行时异常;
智能代码提示:输入键名自动联想补全,提升编码效率;
统一维护:币种新增 / 汇率修改仅需修改一处数据源,无需全页面改代码;
取值简洁:this.rates[‘CNY’]一键取值,比对象字面量可读性更强。
2.2 Select 下拉选择组件(币种选择核心控件)
ArkUI 下拉选择器,动态从 Record 数据源提取币种生成下拉选项,绑定选中事件切换币种后自动触发换算。
// 动态生成下拉选项:Object.keys提取所有币种代码,map转为Select标准格式
Select(Object.keys(this.rates).map(item => ({value:item})))
.value(this.fromCurrency)
.width(120).height(44).fontSize(18).fontWeight(FontWeight.Bold)
.onSelect(index=>{
const arr = Object.keys(this.rates)
this.fromCurrency = arr[index]
this.convert() // 切换币种立刻重新计算
})
2.3 浮点数运算与精度处理(金融类项目必学)
JS/ArkTS 基于 IEEE754 浮点数规范,直接运算会出现0.1+0.2=0.3000000004精度丢失,金融换算必须做小数截断。
// 核心换算逻辑
const usdBase = amountNum / fromRate // 源币种→基准美元
const targetNum = usdBase * toRate // 美元→目标币种
// toFixed保留2位小数,金融场景通用方案
this.result = `${this.symbols[this.toCurrency]}${targetNum.toFixed(2)}`
精度处理 API 汇总:
toFixed(n):保留 n 位小数,返回格式化字符串(本项目主力用法)
Math.round(num*100)/100:四舍五入保留两位小数
parseFloat():输入字符串转浮点数字
isNaN():校验输入是否为非法数字
2.4 数组高阶方法(数据源批量处理)
依托Object.keys/map/filter/find实现数据源快速解析,是 ArkUI 列表渲染必备:
const currencyList = Object.keys(this.rates) // 提取所有币种数组
const selectOpts = currencyList.map(c=> ({value:c})) // 映射组件数据格式
const asianCurr = currencyList.filter(c=>['CNY','JPY','KRW','HKD','TWD'].includes(c)) //筛选亚洲币种
2.5 三种条件渲染写法
根据结果状态动态切换文字颜色、提示文案,项目中三处落地:空输入提示、错误提示、正常结果展示
// 写法1:三元运算(单行文案)
Text(this.result || "请输入金额开始换算")
.fontColor(this.result ? "#059669":"#64748b")
// 写法2:if分支(大块UI)
if(this.result.includes('请输入')){
Text(this.result).fontColor('#ef4444')
}else{
Text(this.result).fontSize(36).fontColor('#059669')
}
// 写法3:&&短路渲染(错误弹窗文字)
{this.showErr && Text('输入不合法').fontColor('red')}
2.6 交互事件体系(全页面交互底层)
项目全覆盖三大常用 ArkUI 事件:输入变更、按钮点击、下拉选中
// 1.输入框实时输入:onChange,边输入边换算
TextInput({text:this.amount,placeholder:'请输入金额'})
.onChange(val=>{this.amount=val;this.convert()})
// 2.交换按钮点击:onClick,互换币种
Button('⇄').onClick(()=>this.swapCurrencies())
// 3.Select下拉:onSelect,前文已演示
2.7 链式样式规范(统一 UI 编码风格)
ArkUI 组件全链式设置尺寸、边距、圆角、配色,遵循鸿蒙官方样式开发规范,代码整洁易维护。
Column(){...}
.width('100%').padding(20).backgroundColor('#fff')
.borderRadius(16).margin({left:20,right:20,bottom:20})
2.8 Column/Row 嵌套布局(页面搭建基石)
Column:垂直纵向排列(标题、输入区、结果面板)
Row:水平横向排列(币种选择左右布局、汇率列表左右文字)
Blank:自动填充剩余空白,实现两端对齐
Row(){
Column(){Text('从');Select(...)}
Blank()
Button('⇄')
Blank()
Column(){Text('到');Select(...)}
}.width('100%').padding(20).justifyContent(FlexAlign.SpaceBetween)
2.9 组件生命周期 aboutToAppear
组件挂载完成自动执行,本项目用于页面初始化默认换算,打开 App 直接展示默认币种结果。
@Component
struct CurrencyPage{
aboutToAppear(){
this.convert() // 页面加载自动计算一次
}
}
2.10 ES 模板字符串
拼接币种符号、数字、汇率文案,替代繁琐字符串拼接:
// 拼接汇率说明:1 USD = 7.24 CNY
const rateDesc = `1 ${this.fromCurrency} = ${(this.rates[this.toCurrency]/this.rates[this.fromCurrency]).toFixed(4)} ${this.toCurrency}`
三、页面结构分步实现 + 完整源码
3.1 全局状态与数据源定义
@Component
export default struct CurrencyConverter {
// 输入金额
@State amount: string = '1';
// 源币种、目标币种默认值
@State fromCurrency: string = 'USD';
@State toCurrency: string = 'CNY';
// 转换结果文案
@State result: string = '';
// 三大数据源
private readonly rates: Record<string, number> = {
'USD': 1.0, 'CNY': 7.24, 'EUR': 0.92, 'JPY': 149.50,
'GBP': 0.79, 'KRW': 1320.0, 'HKD': 7.83, 'TWD': 31.5
}
private readonly symbols: Record<string, string> = {
'USD': '$','CNY': '¥','EUR': '€','JPY': '¥','GBP': '£','KRW': '₩','HKD': 'HK$','TWD': 'NT$'
}
private readonly names: Record<string, string> = {
'USD':'美元','CNY':'人民币','EUR':'欧元','JPY':'日元','GBP':'英镑','KRW':'韩元','HKD':'港币','TWD':'新台币'
}
3.2 核心业务方法:换算 + 币种互换
// 执行换算
private convert() {
const num = parseFloat(this.amount)
// 非法数字拦截
if(isNaN(num)){
this.result = '请输入有效数字金额'
return
}
// 负数拦截
if(num <= 0){
this.result = '金额必须大于0'
return
}
const fromRate = this.rates[this.fromCurrency]
const toRate = this.rates[this.toCurrency]
const usd = num / fromRate
const res = usd * toRate
this.result = `${this.symbols[this.toCurrency]}${res.toFixed(2)}`
}
// 交换源货币与目标货币
private swapCurrencies(){
const temp = this.fromCurrency
this.fromCurrency = this.toCurrency
this.toCurrency = temp
this.convert()
}
// 页面初始化生命周期
aboutToAppear(){
this.convert()
}
3.3 build 页面 UI 构建(分 5 大区块)
build() {
Column() {
// 1.顶部标题
Text('汇率转换')
.fontSize(26).fontWeight(FontWeight.Bold).fontColor('#1e293b')
.margin({top:20,bottom:16})
// 2.金额输入区域
Column(){
Text('输入换算金额').fontSize(14).fontColor('#64748b').width('100%').margin({bottom:8})
TextInput({text:this.amount,placeholder:'输入数字'})
.width('100%').height(52).fontSize(22).textAlign(TextAlign.Center)
.backgroundColor('#fff').borderRadius(12).border({width:1,color:'#e2e8f0'})
.onChange(v=>{this.amount=v;this.convert()})
}.width('92%').margin({bottom:22})
//3.币种选择+互换按钮
Row(){
Column(){
Text('从').fontSize(12).fontColor('#64748b')
Select(Object.keys(this.rates).map(c=>({value:c})))
.value(this.fromCurrency).width(110).height(42).fontSize(17)
.onSelect(i=>{
const arr = Object.keys(this.rates)
this.fromCurrency = arr[i]
this.convert()
})
Text(this.names[this.fromCurrency]).fontSize(12).fontColor('#64748b').margin({top:4})
}.alignItems(HorizontalAlign.Center)
Button('⇄')
.width(56).height(56).backgroundColor('#fff').border({width:2,color:'#059669'})
.borderRadius(28).fontSize(26).fontColor('#059669')
.onClick(()=>this.swapCurrencies())
Column(){
Text('到').fontSize(12).fontColor('#64748b')
Select(Object.keys(this.rates).map(c=>({value:c})))
.value(this.toCurrency).width(110).height(42).fontSize(17)
.onSelect(i=>{
const arr = Object.keys(this.rates)
this.toCurrency = arr[i]
this.convert()
})
Text(this.names[this.toCurrency]).fontSize(12).fontColor('#64748b').margin({top:4})
}.alignItems(HorizontalAlign.Center)
}.width('92%').margin({bottom:24}).justifyContent(FlexAlign.SpaceBetween)
//4.结果展示卡片
Column(){
Text('换算结果').fontSize(14).fontColor('#64748b').width('100%').margin({bottom:10})
Text(this.result).fontSize(38).fontWeight(FontWeight.Bold).fontColor('#059669').margin({bottom:8})
Text(`1 ${this.fromCurrency} = ${(this.rates[this.toCurrency]/this.rates[this.fromCurrency]).toFixed(4)} ${this.toCurrency}`)
.fontSize(13).fontColor('#64748b')
}.width('92%').padding(22).backgroundColor('#fff').borderRadius(16).margin({bottom:20})
//5.常用汇率列表
Column(){
Text('常用基准汇率(兑美元)')
.fontSize(17).fontWeight(FontWeight.Bold).width('100%').margin({bottom:12})
ForEach(['USD','EUR','JPY','GBP'],(item:string)=>{
Row(){
Text(`${item}(${this.names[item]})`).fontSize(14)
Blank()
Text(`${this.symbols[item]}${this.rates[item].toFixed(2)}`).fontColor('#059669')
}.width('100%').padding({top:9,bottom:9})
.border({bottom:{width:1,color:'#eee'}})
})
}.width('92%').padding(20).backgroundColor('#fff').borderRadius(16)
}.width('100%').backgroundColor('#f6faf8')
}
}



四、开发踩坑汇总 & 解决方案
本章为实战踩坑总结,是区分普通博文与优质技术文章关键,罗列 4 个开发高频 BUG
问题 1:输入字母 / 特殊符号结果错乱
原因:parseFloat解析非数字返回 NaN,后续数学运算出现 NAN 结果;
方案:提前isNaN()捕获非法输入,返回自定义错误文案。
问题 2:浮点数计算小数超长(7.24→7.2399999999999)
原因:JS/IEEE754 浮点运算天然精度缺陷;
方案:统一使用.toFixed(2)保留两位小数格式化输出。
问题 3:点击互换按钮,结果不刷新
原因:仅交换变量,未调用convert()重新计算;
方案:swap 函数末尾主动执行this.convert()。
问题 4:Select 下拉文字被截断
原因:组件宽度过小,英文币种名称展示不全;
方案:固定 Select 宽度≥110,适配全部币种字符。
五、项目拓展优化方案
5.1 可新增功能
接入实时汇率 API:使用鸿蒙 http 网络模块,请求公开汇率接口,替换本地静态 rates 数据,增加更新时间展示;
换算历史记录:使用 AppStorage 持久化存储历史换算记录,新增历史列表页面;
币种收藏:自定义收藏常用货币对,首页快捷切换;
离线缓存:联网拉取汇率后本地缓存,无网络使用缓存数据。
5.2 性能 & 体验优化
输入防抖:输入框添加 300ms 防抖,减少频繁 convert 重复计算,优化短时间连续输入性能;
深色模式适配:跟随系统深色 / 浅色主题动态切换页面配色;
快捷数字键盘:自定义纯数字输入面板,屏蔽系统全键盘非数字输入。
六、全文总结
本项目完整落地 ArkUI 声明式开发全流程,通过汇率转换器串联Record 数据管理、Select 组件、状态驱动 UI、浮点数运算、布局嵌套、生命周期、事件绑定等鸿蒙开发核心知识点,所有代码基于 API20 真机调试通过。
掌握 Record 结构化数据管理,告别零散对象定义,适合配置类数据;
学会动态渲染下拉组件,从数据源自动生成 UI 列表;
吃透金融场景浮点精度处理方案,规避开发隐形 BUG;
理解状态 @State 驱动页面刷新的 ArkUI 核心设计思想;
本项目架构可快速复用至单位换算器、计算器、购物计价等同类工具类 App 开发。
更多推荐



所有评论(0)