HarmonyOS NEXT 单位换算器开发:完整技术指南
·
HarmonyOS NEXT 单位换算器开发:完整技术指南
前言
单位换算器是一个经典的工具类应用,涵盖了数据结构设计、算法实现、状态管理、UI交互等多个技术点。本文将从零开始,详细讲解如何使用 HarmonyOS NEXT 开发一个功能完整的单位换算器。
项目信息:
- 包名:
com.example.myapplication - API版本:HarmonyOS NEXT (API 23)
一、需求分析
1.1 功能需求
| 功能 | 描述 |
|---|---|
| 多类别支持 | 长度、重量、温度、面积 |
| 实时换算 | 输入即出结果 |
| 单位选择 | 横向滚动选择器 |
| 单位交换 | 一键互换源/目标单位 |
1.2 单位定义
长度单位(基准:米)
| 单位 | 符号 | 换算系数 |
|---|---|---|
| 毫米 | mm | 0.001 |
| 厘米 | cm | 0.01 |
| 米 | m | 1 |
| 千米 | km | 1000 |
| 英寸 | in | 0.0254 |
| 英尺 | ft | 0.3048 |
重量单位(基准:克)
| 单位 | 符号 | 换算系数 |
|---|---|---|
| 毫克 | mg | 0.001 |
| 克 | g | 1 |
| 千克 | kg | 1000 |
| 吨 | t | 1000000 |
| 磅 | lb | 453.59237 |
| 盎司 | oz | 28.349523125 |
温度单位
| 单位 | 符号 | 换算方式 |
|---|---|---|
| 摄氏度 | °C | 基准 |
| 华氏度 | °F | 特殊公式 |
| 开尔文 | K | 特殊公式 |
面积单位(基准:平方米)
| 单位 | 符号 | 换算系数 |
|---|---|---|
| 平方毫米 | mm² | 0.000001 |
| 平方厘米 | cm² | 0.0001 |
| 平方米 | m² | 1 |
| 平方千米 | km² | 1000000 |
| 公顷 | ha | 10000 |
| 英亩 | acre | 4046.8564224 |
二、数据结构设计
2.1 单位信息接口
interface UnitInfo {
label: string // 显示标签
toBase: number // 换算到基准单位的系数
}
2.2 类别接口
interface Category {
name: string // 类别名称
icon: string // 图标
units: UnitInfo[] // 单位列表
convertFn?: (val: number, fromIdx: number, toIdx: number) => number
}
2.3 数据定义
const CATEGORIES: Category[] = [
{
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 },
]
},
{
name: '重量', icon: '⚖️',
units: [
{ label: '毫克 (mg)', toBase: 0.001 },
{ label: '克 (g)', toBase: 1 },
{ label: '千克 (kg)', toBase: 1000 },
{ label: '吨 (t)', toBase: 1_000_000 },
{ label: '磅 (lb)', toBase: 453.59237 },
{ label: '盎司 (oz)', toBase: 28.349523125 },
]
},
{
name: '温度', icon: '🌡️',
units: [
{ label: '摄氏度 (°C)', toBase: 0 },
{ label: '华氏度 (°F)', toBase: 0 },
{ label: '开尔文 (K)', toBase: 0 },
],
convertFn: (val, fromIdx, toIdx) => {
// 温度特殊换算
let celsius: number
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
}
},
{
name: '面积', icon: '🔲',
units: [
{ label: '平方毫米 (mm²)', toBase: 0.000001 },
{ label: '平方厘米 (cm²)', toBase: 0.0001 },
{ label: '平方米 (m²)', toBase: 1 },
{ label: '平方千米 (km²)', toBase: 1_000_000 },
{ label: '公顷 (ha)', toBase: 10_000 },
{ label: '英亩 (acre)', toBase: 4046.8564224 },
]
},
]
三、换算算法
3.1 基准单位法
对于线性单位(长度、重量、面积),使用基准单位法:
输入值 × toBase[源] = 基准值
基准值 ÷ toBase[目标] = 结果
3.2 代码实现
convert(): void {
const trimmed = this.inputValue.trim()
if (trimmed.length === 0) {
this.resultValue = ''
return
}
const num = parseFloat(trimmed)
if (isNaN(num)) {
this.resultValue = '—'
return
}
const cat = CATEGORIES[this.activeCategory]
if (cat.convertFn) {
// 温度特殊换算
const res = cat.convertFn(num, this.fromIdx, this.toIdx)
this.resultValue = this.formatResult(res)
} else {
// 标准换算
const base = num * cat.units[this.fromIdx].toBase
const res = base / cat.units[this.toIdx].toBase
this.resultValue = this.formatResult(res)
}
}
3.3 结果格式化
formatResult(val: number): string {
if (isNaN(val) || !isFinite(val)) return '—'
// 极小数字
if (Math.abs(val) < 0.000001 && val !== 0) {
return val.toExponential(4)
}
// 极大数字
if (Math.abs(val) > 999999999) {
return val.toExponential(4)
}
// 正常数字:四舍五入去零
const fixed = val.toFixed(6)
return parseFloat(fixed).toString()
}
四、状态管理
4.1 状态定义
@Entry
@Component
struct UnitConverter {
@State activeCategory: number = 0 // 当前类别
@State fromIdx: number = 0 // 源单位索引
@State toIdx: number = 1 // 目标单位索引
@State inputValue: string = '' // 输入值
@State resultValue: string = '' // 结果值
}
4.2 状态联动
// 切换类别
switchCategory(idx: number): void {
this.activeCategory = idx
this.fromIdx = 0
this.toIdx = Math.min(1, CATEGORIES[idx].units.length - 1)
this.convert()
}
// 交换单位
swapUnits(): void {
const tmp = this.fromIdx
this.fromIdx = this.toIdx
this.toIdx = tmp
this.convert()
}
五、UI布局
5.1 整体结构
build() {
Column() {
// 标题
Text('📐 单位换算器')
.fontSize(26)
.fontWeight(FontWeight.Bold)
// 类别Tab
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))
})
}
// 换算卡片
Column() {
// 输入区
Column() { ... }
// 交换按钮
Column() { ... }
// 输出区
Column() { ... }
}
}
}
5.2 单位选择器
Scroll() {
Row() {
ForEach(cat.units, (unit, idx) => {
Text(unit.label)
.fontSize(14)
.fontColor(this.fromIdx === idx ? '#FFFFFF' : '#555555')
.padding({ top: 6, bottom: 6, left: 14, right: 14 })
.backgroundColor(this.fromIdx === idx ? '#5B8DEF' : '#F0F2F5')
.borderRadius(16)
.margin({ right: 8 })
.onClick(() => {
this.fromIdx = idx
this.convert()
})
})
}
}
.scrollable(ScrollDirection.Horizontal)
5.3 输入框
TextInput({ placeholder: '输入数值', text: this.inputValue })
.height(52)
.width('100%')
.type(InputType.Number)
.backgroundColor('#F5F7FA')
.borderRadius(12)
.fontSize(22)
.fontWeight(FontWeight.Bold)
.onChange((val) => {
this.inputValue = val
this.convert()
})
5.4 结果显示
if (this.inputValue.trim().length > 0 && this.resultValue.length > 0) {
Row() {
Text('= ').fontSize(28).fontColor('#5B8DEF')
Text(this.resultValue).fontSize(32).fontWeight(FontWeight.Bold)
Text(' ' + unitLabel).fontSize(16).fontColor('#888888')
}
} else {
Text('请输入数值').fontSize(16).fontColor('#CCCCCC')
}
六、运行效果

七、常见问题
问题1:索引越界
原因:不同类别的单位数量不同。
解决:
this.toIdx = Math.min(1, CATEGORIES[idx].units.length - 1)
问题2:浮点精度
原因:JavaScript浮点数计算误差。
解决:
const fixed = val.toFixed(6)
return parseFloat(fixed).toString()
问题3:温度换算错误
原因:温度不能使用系数法。
解决:使用 convertFn 实现特殊换算。
八、总结
技术要点
| 类别 | 知识点 |
|---|---|
| 数据结构 | interface定义、数组结构 |
| 算法 | 基准单位法、温度特殊处理 |
| 状态管理 | @State装饰器 |
| UI组件 | ForEach、Scroll、TextInput |
项目亮点
- ✅ 实时换算,输入即出结果
- ✅ 智能格式化
- ✅ 横向滚动选择器
- ✅ 卡片式UI设计
完整代码见项目文件,有问题欢迎评论区讨论!
更多推荐


所有评论(0)