鸿蒙开发日记:撸了个单位换算器,顺便把温度换算搞明白了
鸿蒙开发日记:撸了个单位换算器,顺便把温度换算搞明白了
缘起
上周末做饭,菜谱上写的是"盎司",我家厨房秤只显示"克"。掏出手机现找换算工具,心想:这玩意儿我也能做啊!
正好最近在学鸿蒙开发,就当练手项目了。说干就干,一个周末下来,还真让我给撸出来了。
第一天:想清楚要做什么
功能定位
既然是练手,功能不用太复杂,但也得有实用性。我决定支持四大类:
- 长度:mm、cm、m、km、英寸、英尺
- 重量:mg、g、kg、吨、磅、盎司
- 温度:摄氏、华氏、开尔文
- 面积:mm²、cm²、m²、km²、公顷、英亩
这些基本覆盖日常需求了。
UI 怎么设计?
参考了几个主流换算App,发现大家都用类似的布局:
- 顶部类别切换(Tab)
- 输入区域(选单位 + 输数值)
- 输出区域(选单位 + 看结果)
- 交换按钮
简洁明了,我也这么干。
第二天:数据结构,磨刀不误砍柴工
单位怎么定义?
每个单位需要两个属性:名字和换算系数。
interface UnitInfo {
label: string // 显示名称
toBase: number // 换算到基准单位的系数
}
系数是啥意思?
比如长度,我选"米"作为基准:
- 1厘米 = 0.01米,所以
toBase = 0.01 - 1千米 = 1000米,所以
toBase = 1000 - 1英寸 = 0.0254米(对,就是这么精确)
这样换算起来就简单了:
输入值 × toBase = 基准单位值
基准单位值 ÷ 目标toBase = 结果
类别怎么定义?
interface Category {
name: string
icon: string
units: UnitInfo[]
convertFn?: (val, fromIdx, toIdx) => number // 特殊换算函数
}
为什么需要 convertFn?
因为温度不一样!摄氏、华氏、开尔文之间不是简单的乘除关系,需要特殊公式。这个后面细说。
完整数据
const CATEGORIES = [
{
name: '长度', icon: '📏',
units: [
{ label: '毫米 (mm)', toBase: 0.001 },
{ label: '厘米 (cm)', toBase: 0.01 },
{ label: '米 (m)', toBase: 1 },
{ label: '千米 (km)', toBase: 1000 },
{ label: '英寸 (in)', toBase: 0.0254 },
{ label: '英尺 (ft)', toBase: 0.3048 },
]
},
// ... 其他类别
]
第三天:换算算法,基准单位法
普通单位的换算
思路很简单:先把输入转成基准单位,再转成目标单位。
convert(): void {
const num = parseFloat(this.inputValue)
const cat = CATEGORIES[this.activeCategory]
// 输入值 → 基准单位
const base = num * cat.units[this.fromIdx].toBase
// 基准单位 → 目标单位
const result = base / cat.units[this.toIdx].toBase
this.resultValue = this.formatResult(result)
}
举个例子:5千米换算成米
- 输入:5
- 转基准:
5 × 1000 = 5000米 - 转目标:
5000 ÷ 1 = 5000米
完美!
第一个坑:温度不是这么算的
我一开始把温度也用系数法定义了,结果测试时发现:100°C 换算成华氏度,结果是 100,而不是 212。
问题在哪?
温度换算不是线性的!摄氏度和华氏度的关系是:
- °C → °F:
C × 9/5 + 32 - °F → °C:
(F - 32) × 5/9
开尔文也是一样:
- °C → K:
C + 273.15 - K → °C:
K - 273.15
解决方案:
写个特殊函数,先转成摄氏度,再转成目标单位:
convertFn: (val, fromIdx, toIdx) => {
// 第一步:转摄氏度
let celsius
if (fromIdx === 0) celsius = val // 摄氏度
else if (fromIdx === 1) celsius = (val - 32) * 5/9 // 华氏度
else celsius = val - 273.15 // 开尔文
// 第二步:转目标
if (toIdx === 0) return celsius
if (toIdx === 1) return celsius * 9/5 + 32
return celsius + 273.15
}
第四天:UI 布局,卡片式设计
整体结构
Column
├── 标题
├── 类别Tab(Row + ForEach)
├── 换算卡片(Column)
│ ├── 输入区
│ ├── 交换按钮
│ └── 输出区
└── 底部提示
类别Tab
用 ForEach 遍历类别数组,每个Tab是一个 Column:
Row() {
ForEach(CATEGORIES, (cat, idx) => {
Column() {
Text(cat.icon).fontSize(22)
Text(cat.name).fontSize(13)
}
.backgroundColor(this.activeCategory === idx ? '#EEF3FD' : 'transparent')
.onClick(() => { this.switchCategory(idx) })
})
}
.backgroundColor('#F0F2F5')
.borderRadius(12)
选中状态:浅蓝背景 + 蓝字;未选中:透明背景 + 灰字。
单位选择器
这个有点意思,我做成了横向滚动的标签:
Scroll() {
Row() {
ForEach(cat.units, (unit, idx) => {
Text(unit.label)
.backgroundColor(this.fromIdx === idx ? '#5B8DEF' : '#F0F2F5')
.onClick(() => {
this.fromIdx = idx
this.convert()
})
})
}
}
.scrollable(ScrollDirection.Horizontal)
选中的单位:蓝色背景 + 白字;未选中:灰色背景。
输入框
TextInput({ placeholder: '输入数值', text: this.inputValue })
.type(InputType.Number)
.onChange((val) => {
this.inputValue = val
this.convert() // 实时换算!
})
亮点:每次输入都触发换算,不需要点"计算"按钮,输入即出结果。
交换按钮
中间放个按钮,点击互换源和目标单位:
Button() { Text('⇅').fontSize(24) }
.onClick(() => {
const tmp = this.fromIdx
this.fromIdx = this.toIdx
this.toIdx = tmp
this.convert()
})
第五天:细节优化,处理边界情况
第二个坑:浮点数精度
测试时输入 0.1 + 0.2,结果显示 0.30000000000000004。
原因:JavaScript 的经典浮点数精度问题。
解决:用 toFixed 限制精度,再去掉多余的零:
formatResult(val) {
const fixed = val.toFixed(6)
return parseFloat(fixed).toString()
}
第三个坑:单位索引越界
在长度类别选了第6个单位(英尺),切到温度类别时崩溃了。
原因:温度只有3个单位,索引5越界了。
解决:切换类别时重置索引:
switchCategory(idx) {
this.activeCategory = idx
this.fromIdx = 0
this.toIdx = Math.min(1, CATEGORIES[idx].units.length - 1)
}
极大极小数字
比如 1毫米 = 0.000001千米,这显示起来太长了。
解决:科学计数法:
if (Math.abs(val) < 0.000001 && val !== 0) {
return val.toExponential(4)
}
if (Math.abs(val) > 999999999) {
return val.toExponential(4)
}
第六天:测试,各种边界情况

总结
学到了什么
- 数据结构很重要:好的数据结构能让算法简单很多
- 边界情况要考虑:越界、精度、极大极小值
- 实时交互体验好:输入即出结果,比点按钮爽
踩坑总结
| 坑 | 表现 | 解决 |
|---|---|---|
| 温度换算 | 结果错误 | 特殊函数处理 |
| 浮点精度 | 小数位太长 | toFixed + parseFloat |
| 索引越界 | 切换类别崩溃 | 重置索引 + Math.min |
| 极大极小 | 显示太长 | 科学计数法 |
代码量
最后统计了一下,主文件 Index.ets 约 200 行。对于一个完整的应用来说,这个量级很合适——既不复杂,又有技术含量。
开发时间:2天
核心代码:~200行
做菜时再也不用到处找换算工具了,挺有成就感的!
更多推荐
所有评论(0)