码农周末:撸了个单位换算器,被温度换算折腾了一下午
码农周末:撸了个单位换算器,被温度换算折腾了一下午
周末在家做饭,菜谱上写着"牛肉 8 盎司",我家厨房秤只显示克。掏出手机搜了半天,才换算出来是 227 克。
当时就想:这玩意儿我也能做啊!正好最近在学鸿蒙开发,干脆撸一个出来。
说干就干,一个周末下来,还真搞定了。虽然中间被温度换算折腾了一下午,但最终还是做出来了。
一、先想清楚要啥功能
既然是练手项目,功能不用太复杂,但也得实用。我决定支持四大类:
- 长度:mm、cm、m、km、英寸、英尺(装修用得上)
- 重量:mg、g、kg、吨、磅、盎司(做菜用得上)
- 温度:摄氏、华氏、开尔文(看国外天气用得上)
- 面积:mm²、m²、km²、公顷、英亩(买房用得上)
UI 设计嘛,参考了几个主流 App,发现大家都差不多:
- 顶部类别切换
- 输入区:选单位 + 输数值
- 输出区:选单位 + 看结果
- 中间一个交换按钮
简洁明了,我也这么干。
二、数据结构:磨刀不误砍柴工
单位怎么定义?
每个单位需要两个东西:名字和换算系数。
interface UnitInfo {
label: string // "米 (m)"
toBase: number // 换算到基准的系数
}
系数是啥意思?
比如长度,我选"米"作为基准:
- 1厘米 = 0.01米 →
toBase = 0.01 - 1千米 = 1000米 →
toBase = 1000 - 1英寸 = 0.0254米 →
toBase = 0.0254(对,就这么精确)
这样换算就简单了:输入值 × toBase = 基准值,再 基准值 ÷ 目标toBase = 结果。
类别怎么定义?
interface Category {
name: string
icon: string
units: UnitInfo[]
convertFn?: (val, fromIdx, toIdx) => number
}
为啥需要 convertFn?
这个后面细说,是个大坑。
三、换算算法:基准单位法
普通单位的换算
思路很简单:先把输入转成基准单位,再转成目标单位。
// 输入值 → 基准单位
const base = num * cat.units[this.fromIdx].toBase
// 基准单位 → 目标单位
const result = base / cat.units[this.toIdx].toBase
举个例子:5千米换算成米
5 × 1000 = 5000(转基准)5000 ÷ 1 = 5000(转目标)
完美!
第一个大坑:温度不是这么算的
我一开始自信满满,把温度也用系数法定义了。结果一测试:
输入 100°C,换算成华氏度,结果:100。
应该是什么?212°F 啊!
问题在哪?
温度换算不是简单的乘除!摄氏度和华氏度的关系是:
- °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
}
思路是:不管什么温度,先转成摄氏度,再转成目标单位。这样只需要 6 个公式,而不是 9 个(3×3)。
四、UI 布局:卡片式设计
类别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')
})
}
选中状态:浅蓝背景 + 蓝字;未选中:透明 + 灰字。
单位选择器
做了个横向滚动的标签:
Scroll() {
Row() {
ForEach(cat.units, (unit, idx) => {
Text(unit.label)
.backgroundColor(this.fromIdx === idx ? '#5B8DEF' : '#F0F2F5')
})
}
}
.scrollable(ScrollDirection.Horizontal)
选中:蓝色背景 + 白字;未选中:灰色背景。
输入框
TextInput({ placeholder: '输入数值' })
.type(InputType.Number)
.onChange((val) => {
this.inputValue = val
this.convert() // 实时换算!
})
亮点:每次输入都触发换算,不需要点按钮,体验更流畅。
五、更多坑:边界情况
第二个坑:浮点精度
测试时输入 0.1,结果显示 0.10000000000000001。
原因:JavaScript 经典的浮点数精度问题。
解决:toFixed 限制精度,再去零:
const fixed = val.toFixed(6)
return parseFloat(fixed).toString()
第三个坑:索引越界
在长度类别选了第 6 个单位(英尺),切到温度时崩了。
原因:温度只有 3 个单位,索引 5 越界。
解决:切换类别时重置:
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)
}
六、运行效果

七、总结
学到了啥
- 数据结构很重要:好的数据结构能让算法简单很多
- 边界情况要考虑:越界、精度、极大极小值,都是坑
- 实时交互体验好:输入即出结果,比点按钮爽
踩坑记录
| 坑 | 表现 | 解决 |
|---|---|---|
| 温度换算 | 结果错误 | 特殊函数处理 |
| 浮点精度 | 小数位太长 | toFixed + parseFloat |
| 索引越界 | 切换崩溃 | 重置索引 |
| 极值显示 | 太长 | 科学计数法 |
代码量
主文件 Index.ets 约 200 行。对于这种完整应用,这量级刚刚好。
八、后续改进
- 更多单位:体积、速度、时间、货币
- 历史记录:保存最近使用的换算
- 复制功能:一键复制结果
- 暗黑模式:适配系统深色主题
开发时间:一个周末
代码行数:~200 行
做菜时再也不用到处搜换算了,挺有成就感的。有问题欢迎评论区交流!
更多推荐



所有评论(0)