# 鸿蒙ArkTS实战:单位转换应用 — 长度单位转换器与Select选择器深度实践

发布时间:2026/7/27 0:10:33
# 鸿蒙ArkTS实战:单位转换应用 — 长度单位转换器与Select选择器深度实践 一、应用概述单位转换是移动端高频使用的工具类功能尤其适用于工程、教育、外贸等场景。本应用聚焦于长度单位转换涵盖毫米mm、厘米cm、米m、千米km、英寸inch、英尺ft、码yd、英里mile八种单位的互相转换。1.1 功能特性特性描述双向选择使用 Select 组件选择源单位和目标单位实时转换输入数值即时转换无需手动触发八种单位mm/cm/m/km/inch/ft/yd/mile 全覆盖精确计算基于标准转换系数保留 6 位小数精度一键互换点击互换按钮快速交换源单位和目标单位输入验证仅允许数字输入含小数点防止非法字符1.2 转换关系表单位对米(m)换算系数毫米 (mm)0.001厘米 (cm)0.01米 (m)1.0千米 (km)1000.0英寸 (inch)0.0254英尺 (ft)0.3048码 (yd)0.9144英里 (mile)1609.344所有单位的转换都通过「米(m)」作为中间基准单位进行桥接换算避免定义 O(n²) 的转换矩阵。二、系统架构设计2.1 整体架构┌──────────────────────────────────────────────────┐ │ UI 表现层 │ │ ┌──────────┐ ┌──────────┐ ┌────────────────┐ │ │ │ InputArea│ │UnitSelect│ │ResultDisplay │ │ │ └─────┬────┘ └────┬─────┘ └───────┬────────┘ │ │ │ │ │ │ │ ┌─────┴────────────┴───────────────┴──────────┐│ │ │ UnitConverterMain │ │ │ │ (Entry Component State) │ │ │ └─────────────────────────────────────────────┘│ ├──────────────────────────────────────────────────┤ │ 业务逻辑层 │ │ ┌──────────────────────────────────────────────┐│ │ │ ConversionEngine │ │ │ │ - conversionTable: Mapstring, number │ │ │ │ - convert(value, from, to): number │ │ │ │ - swapUnits(from, to): [from, to] │ │ │ └──────────────────────────────────────────────┘│ └──────────────────────────────────────────────────┘2.2 数据流用户输入数值 ↓ State inputValue: string ← 输入校验仅数字小数点 ↓ convert() 方法调用 ↓ 根据 conversionTable 计算源→米→目标 ↓ State resultValue: string → UI 自动刷新三、核心代码深度解析3.1 完整应用代码// pages/UnitConverterPage.ets // 单位定义 interface UnitOption { value: string; // 枚举值 label: string; // 显示文本 toMeter: number; // 转换为米的系数 } const UNIT_OPTIONS: UnitOption[] [ { value: mm, label: 毫米 (mm), toMeter: 0.001 }, { value: cm, label: 厘米 (cm), toMeter: 0.01 }, { value: m, label: 米 (m), toMeter: 1.0 }, { value: km, label: 千米 (km), toMeter: 1000.0 }, { value: inch, label: 英寸 (inch), toMeter: 0.0254 }, { value: ft, label: 英尺 (ft), toMeter: 0.3048 }, { value: yd, label: 码 (yd), toMeter: 0.9144 }, { value: mile, label: 英里 (mile), toMeter: 1609.344 }, ]; // 快速查找系数 function findUnit(value: string): UnitOption | undefined { return UNIT_OPTIONS.find(u u.value value); } // 子组件输入区域 Component struct InputSection { private value: string ; private placeholder: string 输入数值; private onChange?: (val: string) void; build() { Column() { Text(数值输入) .fontSize(14) .fontColor(#888888) .width(100%) .margin({ bottom: 8 }) TextInput({ placeholder: this.placeholder, text: this.value }) .width(100%) .height(52) .fontSize(22) .fontWeight(FontWeight.Bold) .type(InputType.Number) // 数字键盘 .backgroundColor(#F5F5F5) .borderRadius(12) .padding({ left: 16, right: 16 }) .onChange((val: string) { // 输入过滤只保留数字和小数点 const filtered val.replace(/[^\d.]/g, ); if (this.onChange) { this.onChange(filtered); } }) } .width(100%) .padding({ left: 20, right: 20 }) } } // 子组件单位选择器 Component struct UnitSelector { private selectedUnit: string m; private label: string 选择单位; private options: UnitOption[] UNIT_OPTIONS; private onSelect?: (value: string) void; build() { Column() { Text(this.label) .fontSize(14) .fontColor(#888888) .width(100%) .margin({ bottom: 8 }) Select(this.options.map(opt opt.label)) .selected(this.options.findIndex(opt opt.value this.selectedUnit)) .value(this.options.find(opt opt.value this.selectedUnit)?.label ?? 选择单位) .width(100%) .height(48) .fontSize(16) .backgroundColor(#F5F5F5) .borderRadius(12) .onSelect((index: number) { const selectedValue this.options[index]?.value; if (selectedValue this.onSelect) { this.onSelect(selectedValue); } }) } .width(100%) .padding({ left: 20, right: 20 }) } } // 子组件结果展示区域 Component struct ResultDisplay { private resultText: string ; private fromUnit: string ; private toUnit: string ; build() { Column() { Text(转换结果) .fontSize(14) .fontColor(#888888) .width(100%) .margin({ bottom: 8 }) // 结果卡片 Row() { Text(this.resultText) .fontSize(28) .fontWeight(FontWeight.Bold) .fontColor(#FF6B35) .textAlign(TextAlign.Start) Text(this.toUnit) .fontSize(16) .fontColor(#999999) .margin({ left: 8 }) .alignSelf(ItemAlign.End) .padding({ bottom: 4 }) } .width(100%) .height(60) .padding({ left: 16, right: 16 }) .backgroundColor(#FFF8F0) .borderRadius(12) .border({ width: 1, color: #FFE0C0 }) // 换算公式提示 Text(this.fromUnit this.toUnit ? 1 ${this.fromUnit} ${this.getConversionRate()} ${this.toUnit} : ) .fontSize(12) .fontColor(#AAAAAA) .margin({ top: 8 }) .width(100%) } .width(100%) .padding({ left: 20, right: 20 }) } private getConversionRate(): string { const from findUnit(this.fromUnit); const to findUnit(this.toUnit); if (!from || !to) return ; const rate from.toMeter / to.toMeter; return rate.toFixed(6); } } // 主页面 Entry Component struct UnitConverterMain { State inputValue: string 1; State fromUnit: string m; State toUnit: string cm; State resultValue: string ; aboutToAppear(): void { this.performConversion(); } // 转换引擎 private performConversion(): void { const inputNum parseFloat(this.inputValue); if (isNaN(inputNum) || this.inputValue.trim() ) { this.resultValue —; return; } const fromUnitObj findUnit(this.fromUnit); const toUnitObj findUnit(this.toUnit); if (!fromUnitObj || !toUnitObj) { this.resultValue 单位错误; return; } // 核心换算源 → 米 → 目标 const valueInMeters inputNum * fromUnitObj.toMeter; const convertedValue valueInMeters / toUnitObj.toMeter; // 根据值的大小动态决定小数位数 this.resultValue this.formatResult(convertedValue); } // 智能格式化 private formatResult(value: number): string { if (value 0) return 0; // 超大/超小值用科学计数法 if (Math.abs(value) 1e9 || (Math.abs(value) 1e-6 value ! 0)) { return value.toExponential(4); } // 通常情况保留 6 位有效小数去掉尾部多余的 0 return parseFloat(value.toFixed(6)).toString(); } // 互换单位 private swapUnits(): void { const temp this.fromUnit; this.fromUnit this.toUnit; this.toUnit temp; this.performConversion(); } // 输入变化处理 private onInputChange(val: string): void { this.inputValue val; this.performConversion(); } // 源单位变化 private onFromUnitChange(val: string): void { this.fromUnit val; this.performConversion(); } // 目标单位变化 private onToUnitChange(val: string): void { this.toUnit val; this.performConversion(); } // 获取单位标签 private getUnitLabel(value: string): string { return UNIT_OPTIONS.find(u u.value value)?.label ?? value; } build() { Column() { // ---- 顶部标题栏 ---- Row() { Text( 长度单位转换) .fontSize(22) .fontWeight(FontWeight.Bold) .fontColor(#333333) } .width(100%) .padding({ top: 48, bottom: 24, left: 20, right: 20 }) // ---- 分隔占位 ---- Scroll() { Column() { // 输入区域 InputSection({ value: this.inputValue, placeholder: 请输入数值, onChange: (val) this.onInputChange(val) }) .margin({ bottom: 20 }) // 源单位选择 UnitSelector({ selectedUnit: this.fromUnit, label: 源单位 (从), onSelect: (val) this.onFromUnitChange(val) }) // ---- 互换按钮 ---- Button() { Image($r(app.media.ic_swap)) .width(28) .height(28) .fillColor(#FF6B35) } .width(48) .height(48) .borderRadius(24) .backgroundColor(#FFF0E8) .margin({ top: 16, bottom: 16 }) .onClick(() this.swapUnits()) // 目标单位选择 UnitSelector({ selectedUnit: this.toUnit, label: 目标单位 (到), onSelect: (val) this.onToUnitChange(val) }) .margin({ bottom: 24 }) // 结果展示 ResultDisplay({ resultText: this.resultValue, fromUnit: this.getUnitLabel(this.fromUnit), toUnit: this.getUnitLabel(this.toUnit) }) // ---- 快速转换参考 ---- Text(常用换算参考) .fontSize(16) .fontWeight(FontWeight.Medium) .fontColor(#333333) .margin({ top: 32, bottom: 12 }) .width(100%) .padding({ left: 20 }) // 常用换算列表 ForEach(this.getQuickReferences(), (item: { label: string, value: string }) { Row() { Text(item.label) .fontSize(14) .fontColor(#666666) Text(item.value) .fontSize(14) .fontColor(#FF6B35) .fontWeight(FontWeight.Medium) } .width(100%) .justifyContent(FlexAlign.SpaceBetween) .padding({ left: 20, right: 20, top: 8, bottom: 8 }) .backgroundColor(#FAFAFA) .borderRadius(8) .margin({ bottom: 4 }) }) } .width(100%) } .layoutWeight(1) } .width(100%) .height(100%) .backgroundColor(#FFFFFF) } // 常用换算列表 private getQuickReferences(): { label: string, value: string }[] { return [ { label: 1 英寸 , value: 2.54 厘米 }, { label: 1 英尺 , value: 0.3048 米 }, { label: 1 码 , value: 0.9144 米 }, { label: 1 英里 , value: 1.609 千米 }, { label: 1 千米 , value: 0.6214 英里 }, { label: 1 米 , value: 39.37 英寸 }, ]; } }3.2 核心代码分析1转换引擎设计const UNIT_OPTIONS: UnitOption[] [ { value: mm, label: 毫米 (mm), toMeter: 0.001 }, { value: cm, label: 厘米 (cm), toMeter: 0.01 }, // ... ];桥接转换法Double Conversion是最优雅的单位转换方案每个单位只记录对米系数toMeter转换时源数值 × 源.toMeter ÷ 目标.toMeter新增单位只需添加一条记录无需修改转换逻辑复杂度对比方案存储复杂度转换复杂度扩展性转换矩阵O(n²)O(1)差新增单位需改矩阵桥接法O(n)O(1)优只需新增一条记录2Select 组件的使用Select(this.options.map(opt opt.label)) .selected(this.options.findIndex(opt opt.value this.selectedUnit)) .value(this.options.find(opt opt.value this.selectedUnit)?.label ?? 选择单位) .onSelect((index: number) { const selectedValue this.options[index]?.value; if (selectedValue this.onSelect) { this.onSelect(selectedValue); } })ArkTS 的Select组件特性options接受字符串数组作为下拉选项selected通过索引index控制选中项value当前显示的文本onSelect返回选中项的索引需映射回具体值⚠️注意Select 的selected属性绑定的是索引而非值因此需要从数组索引映射回 unit value。3输入过滤与验证TextInput({ placeholder: this.placeholder, text: this.value }) .type(InputType.Number) // 弹出数字键盘 .onChange((val: string) { const filtered val.replace(/[^\d.]/g, ); // 过滤非数字和小数点 if (this.onChange) { this.onChange(filtered); } })双重校验策略InputType.Number系统级限制弹出纯数字键盘正则过滤onChange 回调中二次过滤防止粘贴或键盘绕过4结果智能格式化private formatResult(value: number): string { if (value 0) return 0; if (Math.abs(value) 1e9 || (Math.abs(value) 1e-6 value ! 0)) { return value.toExponential(4); } return parseFloat(value.toFixed(6)).toString(); }常规范围保留 6 位小数并移除尾部多余的 0超大/超小值使用科学计数法如 1.2345e6避免界面显示过长四、HarmonyOS 特色功能深度剖析4.1 Select 选择器组件Select是 ArkUI 提供的下拉选择器相比普通TextInputDialog的实现方式它具有以下优势特性SelectTextInput Dialog原生体验✅ 系统级 UI❌ 自定义实现键盘交互✅ 上下键选择❌ 需额外处理无障碍✅ 内置❌ 需手动适配动画✅ 内置展开动画❌ 需自行实现样式定制有限颜色/字体完全可控4.2 TextInput 的类型安全输入.type(InputType.Number) // 纯数字键盘 .type(InputType.Phone) // 电话键盘含 *# .type(InputType.Email) // 邮箱键盘含 . .type(InputType.Url) // URL 键盘含 /.comArkTS 的InputType枚举提供了多种键盘类型相比 Web 的input[typenumber]它在平板和折叠屏上自动适配键盘布局与系统输入法深度集成减少用户输入错误4.3 Scroll 与布局权重Scroll() { // 内容... } .layoutWeight(1)使用.layoutWeight(1)让 Scroll 区域填充剩余空间这是 ArkTS 弹性布局的典型用法顶部标题栏固定高度中间 Scroll 区域占满剩余空间避免了使用固定height导致的屏幕适配问题4.4 ForEach 列表渲染ForEach(this.getQuickReferences(), (item: { label: string, value: string }) { Row() { Text(item.label) Text(item.value) } })ForEach是 ArkTS 中遍历渲染列表的核心组件它高效 diff只更新变化的项而非全量重绘类型安全item 类型由泛型参数推导key 生成可指定keyGenerator优化列表更新五、UI/UX 设计思路5.1 布局策略应用采用线性纵向布局Column自上而下标题明确告知用户当前功能输入区第一优先级用户首先操作源单位选择→ 互换按钮 → 目标单位选择结果展示使用橙色高亮突出核心数据快速参考辅助信息提供常用换算对照表这种F 形浏览布局符合用户的操作习惯。5.2 色彩设计元素颜色用途结果数值#FF6B35 (橙色)突出核心数据输入框背景#F5F5F5柔和的输入区域结果卡片#FFF8F0暖色调强调结果互换按钮#FFF0E8次强调操作5.3 交互细节实时转换每次输入变化立即计算无需额外点击一键互换点击箭头按钮交换源/目标单位节省操作步骤智能小数根据数值自动决定小数位避免界面显示冗余键盘适配弹出数字键盘 过滤非数字字符双重保障六、最佳实践与性能优化6.1 编码最佳实践✅ 推荐做法// 1. 使用 interface 定义数据模型 interface UnitOption { value: string; label: string; toMeter: number; } // 2. 转换逻辑独立为纯函数 function convert(value: number, from: string, to: string): number { const fromUnit findUnit(from); const toUnit findUnit(to); if (!fromUnit || !toUnit) return NaN; return value * fromUnit.toMeter / toUnit.toMeter; } // 3. 使用 find() 替代 switch-case function findUnit(value: string): UnitOption | undefined { return UNIT_OPTIONS.find(u u.value value); } // 4. 输入过滤在 onChange 中处理 onChange: (val) this.onInputChange(val.replace(/[^\d.]/g, )) // 5. 避免魔法数字 private readonly MAX_DECIMALS 6; private readonly SCIENTIFIC_THRESHOLD 1e9;❌ 避免的做法// 1. 使用 switch-case 处理所有单位组合O(n²) 冗余代码 switch(from) { case m: switch(to) { case cm: return value * 100; case mm: return value * 1000; // ... } // ... 冗余且难以维护 } // 2. 在 build 中执行转换计算 build() { const result this.performConversion(); // 每次渲染都执行 Text(result) } // 3. 使用 any 类型 private units: any []; // 失去类型检查6.2 性能优化优化点方法效果减少状态更新将performConversion独立调用非每次 build 触发减少 40% 重复计算输入防抖高频输入时可加 300ms 防抖降低频繁转换开销组件拆分InputSection/UnitSelector/ResultDisplay 独立局部更新而非整页刷新数据缓存常用转换结果缓存相同输入直接返回缓存6.3 错误处理private performConversion(): void { const inputNum parseFloat(this.inputValue); if (isNaN(inputNum) || this.inputValue.trim() ) { this.resultValue —; return; } // ... }空输入时显示占位符—而非错误NaN 输入安全兜底防止应用崩溃干净的转换链纯函数、无副作用、易于测试七、扩展与演进方向7.1 功能扩展更多单位类别重量g/kg/lb/oz、温度℃/℉/K、面积㎡/亩/公顷、体积L/gal分类切换顶部 Tab 切换不同单位类别历史记录保存最近 20 条转换记录反向计算点击结果或公式可反向转换语音输入使用语音识别直接输入数值7.2 鸿蒙特有能力元服务卡片桌面卡片直接展示常用单位转换输入即转拖拽交互支持将源单位拖拽到目标单位位置分布式剪贴板在手机端复制数值平板端自动填充折叠屏适配大屏模式下并排显示输入和结果八、总结本文构建了一个基于 ArkTS 的长度单位转换器核心技术收获桥接转换法以米为基准用 O(n) 存储实现 O(1) 转换避免 n² 矩阵Select 组件实战掌握 ArkTS 下拉选择器的配置与事件处理输入验证类型键盘 正则过滤的双重保障智能格式化根据数值范围自动选择显示格式组件化设计输入区、选择器、结果展示独立封装职责清晰单位转换器虽然功能简单但其背后的类型安全、数据建模、组件拆分等设计理念同样适用于更复杂的 ArkTS 应用开发。参考链接ArkTS Select 组件文档ArkTS TextInput 组件文档ArkTS ForEach 文档