
基于HarmonyOS的智能单位转换器开发与跨设备同步实现 原创
基于HarmonyOS的智能单位转换器开发与跨设备同步实现
一、项目概述
本项目基于HarmonyOS的ArkUI框架和分布式能力,开发一个支持多设备同步的单位转换器应用,包含长度和重量单位的相互转换功能。参考《鸿蒙跨端U同步:同一局游戏中多设备玩家昵称/头像显示》中的分布式数据同步技术,实现转换记录在多设备间的实时共享。
!https://example.com/unit-converter-arch.png
图1:单位转换器架构(包含UI层、转换逻辑层和分布式数据同步层)
二、核心功能实现
数据模型与转换逻辑(ArkTS)
// 单位类型枚举
enum UnitType {
LENGTH = ‘长度’,
WEIGHT = ‘重量’
// 单位定义接口
interface Unit {
name: string;
symbol: string;
toBase: (value: number) => number; // 转换为基本单位
fromBase: (value: number) => number; // 从基本单位转换
// 转换记录模型
class ConversionRecord {
id: string;
value: number;
fromUnit: Unit;
toUnit: Unit;
result: number;
timestamp: number;
deviceId: string;
constructor(value: number, fromUnit: Unit, toUnit: Unit, result: number) {
this.id = generateUUID();
this.value = value;
this.fromUnit = fromUnit;
this.toUnit = toUnit;
this.result = result;
this.timestamp = Date.now();
this.deviceId = deviceInfo.deviceId;
}
// 单位转换管理器
class UnitConverter {
private static instance: UnitConverter;
private lengthUnits: Unit[] = [];
private weightUnits: Unit[] = [];
private conversionHistory: ConversionRecord[] = [];
static getInstance(): UnitConverter {
if (!UnitConverter.instance) {
UnitConverter.instance = new UnitConverter();
return UnitConverter.instance;
constructor() {
this.initializeUnits();
this.loadHistory();
// 初始化单位定义
private initializeUnits() {
// 长度单位
this.lengthUnits = [
name: ‘毫米’,
symbol: 'mm',
toBase: (v) => v,
fromBase: (v) => v
},
name: ‘厘米’,
symbol: 'cm',
toBase: (v) => v * 10,
fromBase: (v) => v / 10
},
name: ‘米’,
symbol: 'm',
toBase: (v) => v * 1000,
fromBase: (v) => v / 1000
},
name: ‘千米’,
symbol: 'km',
toBase: (v) => v * 1000000,
fromBase: (v) => v / 1000000
},
name: ‘英寸’,
symbol: 'in',
toBase: (v) => v * 25.4,
fromBase: (v) => v / 25.4
},
name: ‘英尺’,
symbol: 'ft',
toBase: (v) => v * 304.8,
fromBase: (v) => v / 304.8
];
// 重量单位
this.weightUnits = [
name: ‘克’,
symbol: 'g',
toBase: (v) => v,
fromBase: (v) => v
},
name: ‘千克’,
symbol: 'kg',
toBase: (v) => v * 1000,
fromBase: (v) => v / 1000
},
name: ‘吨’,
symbol: 't',
toBase: (v) => v * 1000000,
fromBase: (v) => v / 1000000
},
name: ‘磅’,
symbol: 'lb',
toBase: (v) => v * 453.592,
fromBase: (v) => v / 453.592
},
name: ‘盎司’,
symbol: 'oz',
toBase: (v) => v * 28.3495,
fromBase: (v) => v / 28.3495
];
// 执行单位转换
convert(value: number, fromUnit: Unit, toUnit: Unit): number {
if (fromUnit === toUnit) return value;
// 转换为基本单位,再转换为目标单位
const baseValue = fromUnit.toBase(value);
const result = toUnit.fromBase(baseValue);
// 记录转换历史
const record = new ConversionRecord(value, fromUnit, toUnit, result);
this.addToHistory(record);
return result;
// 添加历史记录
addToHistory(record: ConversionRecord) {
this.conversionHistory.unshift(record);
this.saveHistory();
// 获取历史记录
getHistory(): ConversionRecord[] {
return […this.conversionHistory];
// 清空历史记录
clearHistory() {
this.conversionHistory = [];
this.saveHistory();
// 加载历史记录
private loadHistory() {
const history = localStorage.get(‘conversionHistory’);
if (history) {
this.conversionHistory = JSON.parse(history);
}
// 保存历史记录
private saveHistory() {
localStorage.set(‘conversionHistory’, JSON.stringify(this.conversionHistory));
// 获取单位列表
getUnits(type: UnitType): Unit[] {
return type === UnitType.LENGTH ? […this.lengthUnits] : […this.weightUnits];
}
分布式数据同步实现(ArkTS)
// 分布式转换记录同步服务
class ConversionSyncService {
private static instance: ConversionSyncService;
private distObject: distributedDataObject.DataObject;
private deviceList: deviceManager.DeviceInfo[] = [];
static getInstance(): ConversionSyncService {
if (!ConversionSyncService.instance) {
ConversionSyncService.instance = new ConversionSyncService();
return ConversionSyncService.instance;
constructor() {
// 初始化分布式数据对象
this.distObject = distributedDataObject.create({
conversionHistory: [],
deviceList: []
});
// 监听设备连接变化
deviceManager.on('deviceStateChange', () => {
this.updateDeviceList();
});
// 监听数据变化
this.distObject.on('change', (fields: string[]) => {
if (fields.includes('conversionHistory')) {
this.handleHistoryUpdate();
});
this.updateDeviceList();
// 更新设备列表
private updateDeviceList() {
this.deviceList = deviceManager.getConnectedDevices();
this.distObject.deviceList = this.deviceList.map(d => ({
id: d.deviceId,
name: d.deviceName,
lastSync: Date.now()
}));
this.distObject.setDistributed(this.getConnectedDeviceIds());
// 同步转换记录
syncConversion(record: ConversionRecord) {
const history = this.distObject.conversionHistory as ConversionRecord[];
this.distObject.conversionHistory = […history, record];
this.distObject.setDistributed(this.getConnectedDeviceIds());
// 处理历史记录更新
private handleHistoryUpdate() {
const remoteHistory = this.distObject.conversionHistory as ConversionRecord[];
const localHistory = UnitConverter.getInstance().getHistory();
// 合并策略:保留最新的100条记录
const mergedHistory = [...localHistory, ...remoteHistory]
.sort((a, b) => b.timestamp - a.timestamp)
.filter((v, i, a) => a.findIndex(t => t.id = v.id) = i)
.slice(0, 100);
UnitConverter.getInstance().updateHistory(mergedHistory);
// 获取已连接设备ID
private getConnectedDeviceIds(): string[] {
return this.deviceList
.map(d => d.deviceId)
.filter(id => id !== deviceInfo.deviceId);
// 获取设备名称
getDeviceName(deviceId: string): string {
if (deviceId === deviceInfo.deviceId) return ‘本机’;
const device = this.deviceList.find(d => d.deviceId === deviceId);
return device?.deviceName || '其他设备';
}
UI界面实现(ArkTS)
// 主页面组件
@Entry
@Component
struct UnitConverterPage {
@State currentTab: UnitType = UnitType.LENGTH;
@State inputValue: string = ‘’;
@State fromUnit: Unit = UnitConverter.getInstance().getUnits(UnitType.LENGTH)[0];
@State toUnit: Unit = UnitConverter.getInstance().getUnits(UnitType.LENGTH)[1];
@State result: number = 0;
@State showHistory: boolean = false;
private converter = UnitConverter.getInstance();
private syncService = ConversionSyncService.getInstance();
build() {
Column() {
// 标题栏
Row() {
Text(‘单位转换器’)
.fontSize(24)
.fontWeight(FontWeight.Bold)
// 设备同步状态
DeviceSyncIndicator()
.width(‘100%’)
.justifyContent(FlexAlign.SpaceBetween)
.padding(16)
// 标签页
Tabs({ barPosition: BarPosition.Start }) {
TabContent() {
this.buildConverterContent(UnitType.LENGTH)
}.tabBar('长度')
TabContent() {
this.buildConverterContent(UnitType.WEIGHT)
}.tabBar('重量')
.onChange((index: number) => {
this.currentTab = index === 0 ? UnitType.LENGTH : UnitType.WEIGHT;
this.resetUnits();
})
.width('100%')
.layoutWeight(1)
.height(‘100%’)
.backgroundColor('#F5F5F5')
// 构建转换器内容
@Builder
buildConverterContent(type: UnitType) {
Column() {
// 输入区域
Row() {
TextInput({ placeholder: ‘输入数值’ })
.width(‘40%’)
.type(InputType.Number)
.onChange((value: string) => {
this.inputValue = value;
this.calculateResult();
})
UnitPicker({
units: this.converter.getUnits(type),
selectedUnit: this.fromUnit,
onUnitChange: (unit: Unit) => {
this.fromUnit = unit;
this.calculateResult();
})
Text('→')
.fontSize(20)
.margin({ left: 8, right: 8 })
UnitPicker({
units: this.converter.getUnits(type),
selectedUnit: this.toUnit,
onUnitChange: (unit: Unit) => {
this.toUnit = unit;
this.calculateResult();
})
.width(‘100%’)
.padding(16)
// 结果展示
if (this.inputValue) {
Column() {
Text({this.inputValue} {this.fromUnit.symbol} =)
.fontSize(18)
Text({this.result.toFixed(6)} {this.toUnit.symbol})
.fontSize(24)
.fontWeight(FontWeight.Bold)
.margin({ top: 8 })
.width(‘100%’)
.padding(16)
.backgroundColor('#FFFFFF')
.borderRadius(8)
// 操作按钮
Row() {
Button('历史记录')
.width('40%')
.onClick(() => {
this.showHistory = true;
})
Button('交换单位')
.width('40%')
.margin({ left: 16 })
.onClick(() => {
const temp = this.fromUnit;
this.fromUnit = this.toUnit;
this.toUnit = temp;
this.calculateResult();
})
.width(‘100%’)
.padding(16)
.justifyContent(FlexAlign.Center)
// 历史记录弹窗
if (this.showHistory) {
HistoryPanel({
history: this.converter.getHistory(),
onClose: () => {
this.showHistory = false;
})
}
// 计算转换结果
private calculateResult() {
if (!this.inputValue) return;
const value = parseFloat(this.inputValue);
if (isNaN(value)) return;
this.result = this.converter.convert(value, this.fromUnit, this.toUnit);
this.syncService.syncConversion(
new ConversionRecord(value, this.fromUnit, this.toUnit, this.result)
);
// 重置单位选择
private resetUnits() {
const units = this.converter.getUnits(this.currentTab);
this.fromUnit = units[0];
this.toUnit = units[1];
this.result = 0;
}
// 单位选择器组件
@Component
struct UnitPicker {
@Prop units: Unit[];
@Prop selectedUnit: Unit;
@Link onUnitChange: (unit: Unit) => void;
@State showPicker: boolean = false;
build() {
Column() {
Button(this.selectedUnit.name)
.width(120)
.onClick(() => {
this.showPicker = true;
})
if (this.showPicker) {
Column() {
ForEach(this.units, (unit: Unit) => {
Button(unit.name)
.width('100%')
.backgroundColor(unit === this.selectedUnit ? '#E3F2FD' : '#FFFFFF')
.onClick(() => {
this.onUnitChange(unit);
this.showPicker = false;
})
})
.width(120)
.border({ width: 1, color: '#E0E0E0' })
.borderRadius(4)
.margin({ top: 4 })
}
}
// 历史记录面板组件
@Component
struct HistoryPanel {
@Prop history: ConversionRecord[];
@Link onClose: () => void;
private syncService = ConversionSyncService.getInstance();
build() {
Column() {
// 标题栏
Row() {
Text(‘转换历史’)
.fontSize(20)
.fontWeight(FontWeight.Bold)
Button('关闭')
.onClick(() => {
this.onClose();
})
.width(‘100%’)
.justifyContent(FlexAlign.SpaceBetween)
.padding(16)
// 历史记录列表
List({ space: 8 }) {
ForEach(this.history, (record: ConversionRecord) => {
ListItem() {
HistoryItem({ record: record })
})
.layoutWeight(1)
.width('100%')
// 清空按钮
Button('清空历史记录')
.width('80%')
.margin(16)
.onClick(() => {
UnitConverter.getInstance().clearHistory();
})
.width(‘90%’)
.height('70%')
.backgroundColor('#FFFFFF')
.borderRadius(16)
.position({ x: '5%', y: '15%' })
}
// 历史记录项组件
@Component
struct HistoryItem {
@Prop record: ConversionRecord;
private syncService = ConversionSyncService.getInstance();
build() {
Row() {
Column() {
Text({this.record.value} {this.record.fromUnit.symbol} → {this.record.result.toFixed(4)} {this.record.toUnit.symbol})
.fontSize(16)
Row() {
Text(formatTime(this.record.timestamp))
.fontSize(12)
.fontColor('#757575')
Text(来自: ${this.syncService.getDeviceName(this.record.deviceId)})
.fontSize(12)
.fontColor('#757575')
.margin({ left: 8 })
.margin({ top: 4 })
.layoutWeight(1)
.width(‘100%’)
.padding(16)
.backgroundColor('#FAFAFA')
.borderRadius(8)
}
// 设备同步状态指示器组件
@Component
struct DeviceSyncIndicator {
@State connectedDevices: number = 0;
aboutToAppear() {
deviceManager.on(‘deviceStateChange’, () => {
this.connectedDevices = deviceManager.getConnectedDevices().length;
});
this.connectedDevices = deviceManager.getConnectedDevices().length;
build() {
Row() {
Image($r('app.media.ic_sync'))
.width(16)
.height(16)
Text(${this.connectedDevices})
.fontSize(16)
.margin({ left: 4 })
}
// 辅助函数:格式化时间
function formatTime(timestamp: number): string {
const date = new Date(timestamp);
return {date.getMonth()+1}/{date.getDate()} {date.getHours()}:{date.getMinutes().toString().padStart(2, ‘0’)};
三、关键功能说明
单位转换核心算法
// 转换算法实现
convert(value: number, fromUnit: Unit, toUnit: Unit): number {
if (fromUnit === toUnit) return value;
// 转换为基本单位,再转换为目标单位
const baseValue = fromUnit.toBase(value);
const result = toUnit.fromBase(baseValue);
return result;
分布式数据同步流程
数据同步触发:
// 转换完成后同步记录
this.syncService.syncConversion(
new ConversionRecord(value, fromUnit, toUnit, result)
);
数据接收处理:
// 处理远程更新
private handleHistoryUpdate() {
const remoteHistory = this.distObject.conversionHistory as ConversionRecord[];
// 合并到本地历史…
数据类型转换实践
转换场景 实现方式 注意事项
字符串→数字 parseFloat(inputValue) 处理NaN情况
数字→字符串 result.toFixed(6) 控制小数位数
单位间转换 通过基本单位中转 保持精度
四、项目扩展与优化
功能扩展建议
更多单位类型:
enum UnitType {
LENGTH = '长度',
WEIGHT = '重量',
TEMPERATURE = '温度',
VOLUME = '体积'
收藏常用转换:
interface FavoriteConversion {
fromUnit: Unit;
toUnit: Unit;
实时货币转换:
// 集成汇率API实现货币转换
性能优化建议
历史记录分页:
getHistory(page: number, pageSize: number): ConversionRecord[] {
return this.conversionHistory.slice(
(page - 1) * pageSize,
page * pageSize
);
数据同步优化:
添加节流机制
实现差异同步
使用二进制格式传输
五、测试方案
测试用例设计
测试类型 测试场景 验证点
功能测试 单位转换 结果计算准确
功能测试 历史记录 正确保存和显示
同步测试 多设备转换 历史记录同步
性能测试 大数据量 列表滚动流畅
兼容测试 不同设备 布局适配正常
自动化测试示例
// 单位转换测试
describe(‘UnitConverter Tests’, () => {
let converter: UnitConverter;
before(() => {
converter = UnitConverter.getInstance();
});
it(‘should convert meters to centimeters’, () => {
const meters = converter.getUnits(UnitType.LENGTH)[2]; // 米
const centimeters = converter.getUnits(UnitType.LENGTH)[1]; // 厘米
const result = converter.convert(1, meters, centimeters);
expect(result).toBe(100);
});
it(‘should sync conversion records’, () => {
const syncService = ConversionSyncService.getInstance();
const record = new ConversionRecord(1, meters, centimeters, 100);
syncService.syncConversion(record);
expect(syncService['distObject'].conversionHistory.length).toBe(1);
});
});
六、总结
本项目基于HarmonyOS开发了一个功能完善的单位转换器,主要特点包括:
精准的单位转换:支持多种长度和重量单位的相互转换
完整的历史记录:保存每次转换记录并支持跨设备同步
直观的用户界面:清晰的输入输出和单位选择
分布式能力集成:实现多设备间的转换记录共享
通过参考《鸿蒙跨端U同步:同一局游戏中多设备玩家昵称/头像显示》的技术方案,我们验证了HarmonyOS在分布式数据同步方面的强大能力,为开发者提供了构建跨设备协同应用的实践参考。
注意事项:
实际开发中需要处理浮点数精度问题
考虑添加单位换算公式说明
生产环境需要更完善的错误处理
可根据需求扩展更多单位类型
