目录

  1. 概述
  2. 工具功能
  3. 核心实现
  4. 实战案例
  5. 编译过程详解
  6. 工具扩展
  7. 最佳实践
  8. 常见问题

概述

本文档介绍如何在 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 方法

关键转换点

  1. Map 结构:转换为 JavaScript 对象
  2. 条件判断:转换为 if-else 语句
  3. 集合操作:转换为数组操作
  4. 字符串处理:保持功能一致

工具扩展

扩展 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

下一步

  1. 添加更多单位类型
  2. 实现单位别名
  3. 添加历史记录
  4. 实现批量转换
  5. 添加自定义单位

参考资源

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐