KMP 实现鸿蒙跨端:Kotlin 单位转换工具
·
目录
概述
本文档介绍如何在 Kotlin Multiplatform (KMP) 鸿蒙跨端开发中实现一个完整的单位转换工具系统。这个案例展示了如何使用 Kotlin 的 Map 数据结构、数学计算和字符串处理来创建一个功能丰富的单位转换工具。通过 KMP,这个工具可以无缝编译到 JavaScript,在 OpenHarmony 应用中运行,并支持用户输入进行实时处理。
工具的特点
- 多种单位支持:支持长度、重量、温度、体积等多种单位
- 灵活转换:在同类单位之间进行转换
- 实时处理:支持用户输入进行实时转换
- 详细结果:显示所有相关单位的转换结果
- 跨端兼容:一份 Kotlin 代码可同时服务多个平台
工具功能
1. 长度转换
- 支持单位:mm、cm、m、km、in、ft、yd、mi
- 基准单位:米 (m)
- 精度:4 位小数
2. 重量转换
- 支持单位:mg、g、kg、oz、lb、ton
- 基准单位:克 (g)
- 精度:4 位小数
3. 温度转换
- 支持单位:c (摄氏度)、f (华氏度)、k (开尔文)
- 双向转换:支持任意单位间转换
- 精度:2 位小数
4. 体积转换
- 支持单位:ml、l、fl_oz、pint、gallon
- 基准单位:升 (l)
- 精度:4 位小数
5. 输入验证
- 格式检查:验证输入格式
- 数值检查:确保数值有效
- 单位检查:验证单位是否支持
核心实现
1. 输入解析
val parts = inputValue.trim().split(" ")
val value = parts[0].toDoubleOrNull()
val unit = parts[1].lowercase()
2. 转换映射
val lengthConversions = mapOf(
"mm" to 0.001,
"cm" to 0.01,
"m" to 1.0,
"km" to 1000.0,
"in" to 0.0254,
"ft" to 0.3048,
"yd" to 0.9144,
"mi" to 1609.34
)
3. 基准值计算
val baseValue = value * lengthConversions[unit]!!
4. 转换结果生成
val conversions = lengthConversions.map { (u, factor) ->
val converted = baseValue / factor
" $u: ${(converted * 10000).toInt() / 10000.0}"
}
5. 温度特殊处理
val celsius = when (unit) {
"c" -> value
"f" -> (value - 32) * 5 / 9
"k" -> value - 273.15
else -> 0.0
}
实战案例
案例:完整的单位转换工具
Kotlin 源代码
@OptIn(ExperimentalJsExport::class)
@JsExport
fun unitConverterTool(inputValue: String = "100 km"): String {
if (inputValue.isEmpty()) {
return "❌ 错误: 输入不能为空\n请输入数值和单位 (如: 100 km)"
}
val parts = inputValue.trim().split(" ")
if (parts.size < 2) {
return "❌ 错误: 格式不正确\n请输入数值和单位 (如: 100 km)"
}
val value = parts[0].toDoubleOrNull()
if (value == null) {
return "❌ 错误: 数值无效\n请输入有效的数字"
}
val unit = parts[1].lowercase()
val lengthConversions = mapOf(
"mm" to 0.001, "cm" to 0.01, "m" to 1.0, "km" to 1000.0,
"in" to 0.0254, "ft" to 0.3048, "yd" to 0.9144, "mi" to 1609.34
)
val weightConversions = mapOf(
"mg" to 0.001, "g" to 1.0, "kg" to 1000.0,
"oz" to 28.3495, "lb" to 453.592, "ton" to 1000000.0
)
val temperatureUnits = setOf("c", "f", "k")
val volumeConversions = mapOf(
"ml" to 0.001, "l" to 1.0, "fl_oz" to 0.0295735,
"pint" to 0.473176, "gallon" to 3.78541
)
val result = when {
unit in lengthConversions -> {
val baseValue = value * lengthConversions[unit]!!
val conversions = lengthConversions.map { (u, factor) ->
val converted = baseValue / factor
" $u: ${(converted * 10000).toInt() / 10000.0}"
}
"📏 长度转换\n" +
"━━━━━━━━━━━━━━━━━━━━━\n" +
"输入: $value $unit\n" +
"基准值 (米): ${(baseValue * 10000).toInt() / 10000.0}\n\n" +
"转换结果:\n" +
conversions.joinToString("\n")
}
unit in weightConversions -> {
val baseValue = value * weightConversions[unit]!!
val conversions = weightConversions.map { (u, factor) ->
val converted = baseValue / factor
" $u: ${(converted * 10000).toInt() / 10000.0}"
}
"⚖️ 重量转换\n" +
"━━━━━━━━━━━━━━━━━━━━━\n" +
"输入: $value $unit\n" +
"基准值 (克): ${(baseValue * 10000).toInt() / 10000.0}\n\n" +
"转换结果:\n" +
conversions.joinToString("\n")
}
unit in temperatureUnits -> {
val celsius = when (unit) {
"c" -> value
"f" -> (value - 32) * 5 / 9
"k" -> value - 273.15
else -> 0.0
}
val fahrenheit = celsius * 9 / 5 + 32
val kelvin = celsius + 273.15
"🌡️ 温度转换\n" +
"━━━━━━━━━━━━━━━━━━━━━\n" +
"输入: $value $unit\n\n" +
"转换结果:\n" +
" 摄氏度 (°C): ${(celsius * 100).toInt() / 100.0}\n" +
" 华氏度 (°F): ${(fahrenheit * 100).toInt() / 100.0}\n" +
" 开尔文 (K): ${(kelvin * 100).toInt() / 100.0}"
}
unit in volumeConversions -> {
val baseValue = value * volumeConversions[unit]!!
val conversions = volumeConversions.map { (u, factor) ->
val converted = baseValue / factor
" $u: ${(converted * 10000).toInt() / 10000.0}"
}
"🧃 体积转换\n" +
"━━━━━━━━━━━━━━━━━━━━━\n" +
"输入: $value $unit\n" +
"基准值 (升): ${(baseValue * 10000).toInt() / 10000.0}\n\n" +
"转换结果:\n" +
conversions.joinToString("\n")
}
else -> "❌ 错误: 不支持的单位\n支持的单位:\n" +
"长度: mm, cm, m, km, in, ft, yd, mi\n" +
"重量: mg, g, kg, oz, lb, ton\n" +
"温度: c, f, k\n" +
"体积: ml, l, fl_oz, pint, gallon"
}
return result + "\n\n━━━━━━━━━━━━━━━━━━━━━\n✅ 转换完成!"
}
ArkTS 调用代码(带输入框)
import { unitConverterTool } from './hellokjs';
@Entry
@Component
struct Index {
@State message: string = '加载中...';
@State results: string[] = [];
@State caseTitle: string = '单位转换工具';
@State inputText: string = '100 km';
aboutToAppear(): void {
this.loadResults();
}
loadResults(): void {
try {
const results: string[] = [];
const algorithmResult = unitConverterTool(this.inputText);
results.push(algorithmResult);
this.results = results;
this.message = '✓ 转换完成';
} catch (error) {
this.message = `✗ 错误: ${error}`;
}
}
build() {
Column() {
// 顶部标题栏
Row() {
Text('KMP 鸿蒙跨端')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
Spacer()
Text('Kotlin 案例')
.fontSize(14)
.fontColor(Color.White)
}
.width('100%')
.height(50)
.backgroundColor('#3b82f6')
.padding({ left: 20, right: 20 })
.alignItems(VerticalAlign.Center)
.justifyContent(FlexAlign.SpaceBetween)
// 案例标题
Column() {
Text(this.caseTitle)
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor('#1f2937')
Text(this.message)
.fontSize(13)
.fontColor('#6b7280')
.margin({ top: 5 })
}
.width('100%')
.padding({ left: 20, right: 20, top: 20, bottom: 15 })
.alignItems(HorizontalAlign.Start)
// 输入框区域
Column() {
Text('输入数值和单位:')
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor('#1f2937')
.margin({ bottom: 8 })
TextInput({ placeholder: '输入数值和单位 (如: 100 km)...', text: this.inputText })
.width('100%')
.height(60)
.padding(12)
.border({ width: 1, color: '#d1d5db' })
.borderRadius(6)
.onChange((value: string) => {
this.inputText = value
})
Button('转换')
.width('100%')
.height(40)
.margin({ top: 12 })
.backgroundColor('#3b82f6')
.fontColor(Color.White)
.onClick(() => {
this.loadResults()
})
}
.width('100%')
.padding({ left: 16, right: 16, bottom: 16 })
// 结果显示区域
Scroll() {
Column() {
ForEach(this.results, (result: string) => {
Column() {
Text(result)
.fontSize(13)
.fontFamily('monospace')
.fontColor('#374151')
.width('100%')
.margin({ top: 10 })
}
.width('100%')
.padding(16)
.backgroundColor(Color.White)
.border({ width: 1, color: '#e5e7eb' })
.borderRadius(8)
.margin({ bottom: 12 })
})
}
.width('100%')
.padding({ left: 16, right: 16 })
}
.layoutWeight(1)
.width('100%')
// 底部按钮区域
Row() {
Button('示例转换')
.width('48%')
.height(44)
.backgroundColor('#10b981')
.fontColor(Color.White)
.fontSize(14)
.onClick(() => {
this.inputText = '100 km'
this.loadResults()
})
Button('清空')
.width('48%')
.height(44)
.backgroundColor('#6b7280')
.fontColor(Color.White)
.fontSize(14)
.onClick(() => {
this.inputText = ''
this.results = []
})
}
.width('100%')
.padding({ left: 16, right: 16, bottom: 20 })
}
.width('100%')
.height('100%')
.backgroundColor('#f9fafb')
}
}
编译过程详解
Kotlin 到 JavaScript 的转换
| Kotlin 特性 | JavaScript 等价物 |
|---|---|
| mapOf() | 对象字面量 |
| in 操作符 | in 操作符 |
| when 表达式 | if-else 语句 |
| map() | 数组 map 方法 |
| joinToString() | 数组 join 方法 |
关键转换点
- Map 结构:转换为 JavaScript 对象
- 条件判断:转换为 if-else 语句
- 集合操作:转换为数组操作
- 字符串处理:保持功能一致
工具扩展
扩展 1:添加压力单位
val pressureConversions = mapOf(
"pa" to 1.0,
"kpa" to 1000.0,
"bar" to 100000.0,
"atm" to 101325.0,
"psi" to 6894.76
)
扩展 2:添加速度单位
val speedConversions = mapOf(
"m/s" to 1.0,
"km/h" to 0.277778,
"mph" to 0.44704,
"knot" to 0.514444
)
扩展 3:添加面积单位
val areaConversions = mapOf(
"m2" to 1.0,
"km2" to 1000000.0,
"hectare" to 10000.0,
"acre" to 4046.86,
"sq_mile" to 2589988.0
)
扩展 4:添加能量单位
val energyConversions = mapOf(
"j" to 1.0,
"kj" to 1000.0,
"cal" to 4.184,
"kcal" to 4184.0,
"kwh" to 3600000.0
)
最佳实践
1. 使用 Map 存储转换因子
// ✅ 好:使用 Map
val conversions = mapOf("mm" to 0.001, "cm" to 0.01)
// ❌ 不好:使用多个变量
val mmToM = 0.001
val cmToM = 0.01
2. 使用 in 操作符检查
// ✅ 好:使用 in
if (unit in conversions) { }
// ❌ 不好:使用 containsKey
if (conversions.containsKey(unit)) { }
3. 使用 when 表达式
// ✅ 好:使用 when
val result = when {
unit in lengthConversions -> "长度"
unit in weightConversions -> "重量"
else -> "未知"
}
// ❌ 不好:使用多个 if
var result = ""
if (unit in lengthConversions) result = "长度"
else if (unit in weightConversions) result = "重量"
4. 验证输入
// ✅ 好:验证输入
val value = parts[0].toDoubleOrNull()
if (value == null) return "❌ 错误"
// ❌ 不好:不验证
val value = parts[0].toDouble() // 可能出错
常见问题
Q1: 如何添加新的单位类型?
A: 创建新的 Map 并在 when 表达式中添加条件:
val newConversions = mapOf(
"unit1" to 1.0,
"unit2" to 2.0
)
val result = when {
unit in newConversions -> { /* 处理 */ }
else -> "未知"
}
Q2: 如何处理浮点数精度问题?
A: 使用整数乘法和除法来保留小数位:
val rounded = (value * 10000).toInt() / 10000.0
Q3: 如何支持大小写不敏感的单位?
A: 在解析时转换为小写:
val unit = parts[1].lowercase()
Q4: 如何添加单位别名?
A: 在 Map 中添加多个键指向相同的值:
val conversions = mapOf(
"km" to 1000.0,
"kilometer" to 1000.0,
"千米" to 1000.0
)
Q5: 如何实现反向转换?
A: 交换输入和输出的单位:
// 从 km 转换为 m
val fromKm = 100 * 1000.0 // 100000 m
val toM = fromKm / 1.0 // 100000 m
总结
关键要点
- ✅ 使用 Map 存储转换因子
- ✅ 使用 in 操作符检查单位
- ✅ 使用 when 表达式处理不同类型
- ✅ 验证用户输入
- ✅ KMP 能无缝编译到 JavaScript
下一步
- 添加更多单位类型
- 实现单位别名
- 添加历史记录
- 实现批量转换
- 添加自定义单位
参考资源
更多推荐



所有评论(0)