Flutter 框架跨平台鸿蒙开发 - 打造专业级单位换算器,支持8大类50+单位互转

Flutter实战:打造专业级单位换算器,支持8大类50+单位互转

单位换算是日常生活和工程计算中的高频需求。本文将手把手教你用Flutter构建一款功能完备的单位换算器,涵盖长度、面积、体积、重量、温度、时间、速度、数据存储等8大类别,支持50多种单位的实时互转。

效果预览




这款单位换算器具备以下特点:

  • 🎯 8大类别:长度、面积、体积、重量、温度、时间、速度、数据
  • 🔄 实时换算:输入即转换,无需点击按钮
  • 📊 全量展示:一次显示所有单位的换算结果
  • 🌡️ 温度特殊处理:摄氏度、华氏度、开尔文三者互转
  • 💫 流畅交互:单位选择器、交换按钮、清除功能

架构设计

逻辑层
视图层
数据层
UnitCategory
UnitType
UnitData
UnitConverterApp
CategorySelector
ConverterPage
InputCard
ConversionCard
AllResultsCard
_convert
通用换算
温度换算
_formatNumber
科学计数法
小数处理

核心数据模型

单位类别与单位类型

单位换算的核心在于建立统一的数据模型。每个类别包含多个单位,每个单位都有一个到基准单位的转换系数。

dart 复制代码
/// 单位类别
class UnitCategory {
  final String name;      // 类别名称
  final IconData icon;    // 图标
  final Color color;      // 主题色
  final List<UnitType> units;  // 包含的单位列表

  const UnitCategory({
    required this.name,
    required this.icon,
    required this.color,
    required this.units,
  });
}

/// 单位类型
class UnitType {
  final String name;      // 单位名称
  final String symbol;    // 单位符号
  final double toBase;    // 转换到基准单位的系数

  const UnitType({
    required this.name,
    required this.symbol,
    required this.toBase,
  });
}

单位数据定义

以长度单位为例,选择"米"作为基准单位(toBase = 1),其他单位的系数表示"1个该单位等于多少米":

单位 符号 转换系数 说明
千米 km 1000 1km = 1000m
m 1 基准单位
厘米 cm 0.01 1cm = 0.01m
英里 mi 1609.344 1mi ≈ 1609m
英尺 ft 0.3048 1ft ≈ 0.3m
dart 复制代码
// 长度单位定义
UnitCategory(
  name: '长度',
  icon: Icons.straighten,
  color: Colors.blue,
  units: [
    UnitType(name: '千米', symbol: 'km', toBase: 1000),
    UnitType(name: '米', symbol: 'm', toBase: 1),
    UnitType(name: '分米', symbol: 'dm', toBase: 0.1),
    UnitType(name: '厘米', symbol: 'cm', toBase: 0.01),
    UnitType(name: '毫米', symbol: 'mm', toBase: 0.001),
    UnitType(name: '英里', symbol: 'mi', toBase: 1609.344),
    UnitType(name: '码', symbol: 'yd', toBase: 0.9144),
    UnitType(name: '英尺', symbol: 'ft', toBase: 0.3048),
    UnitType(name: '英寸', symbol: 'in', toBase: 0.0254),
    UnitType(name: '海里', symbol: 'nmi', toBase: 1852),
  ],
),

换算算法详解

通用换算公式

对于大多数单位,换算遵循简单的线性关系:

结果=输入值×源单位系数目标单位系数 结果 = 输入值 \times \frac{源单位系数}{目标单位系数} 结果=输入值×目标单位系数源单位系数

换算过程分两步:

  1. 转为基准单位 :基准值=输入值×源单位系数基准值 = 输入值 \times 源单位系数基准值=输入值×源单位系数
  2. 转为目标单位 :结果=基准值目标单位系数结果 = \frac{基准值}{目标单位系数}结果=目标单位系数基准值
dart 复制代码
void _convert() {
  final input = double.tryParse(_inputController.text) ?? 0;
  final fromUnit = widget.category.units[_fromUnitIndex];
  final toUnit = widget.category.units[_toUnitIndex];

  double result;
  if (widget.category.name == '温度') {
    result = _convertTemperature(input, fromUnit.symbol, toUnit.symbol);
  } else {
    // 通用换算:先转到基准单位,再转到目标单位
    final baseValue = input * fromUnit.toBase;
    result = baseValue / toUnit.toBase;
  }

  setState(() {
    _result = _formatNumber(result);
    _calculateAllResults(input);
  });
}

温度换算的特殊处理

温度单位之间不是简单的倍数关系,需要特殊处理。三种温度单位的转换公式:
× 9/5 + 32
(F-32) × 5/9

  • 273.15 - 273.15 摄氏度 °C
    华氏度 °F
    开尔文 K

转换策略:以摄氏度为中转站,任意两个温度单位的转换都先转为摄氏度,再转为目标单位。

°F=°C×95+32K=°C+273.15 \begin{aligned} °F &= °C \times \frac{9}{5} + 32 \\ K &= °C + 273.15 \end{aligned} °FK=°C×59+32=°C+273.15

dart 复制代码
double _convertTemperature(double value, String from, String to) {
  // 第一步:转换为摄氏度
  double celsius;
  switch (from) {
    case '°C':
      celsius = value;
      break;
    case '°F':
      celsius = (value - 32) * 5 / 9;
      break;
    case 'K':
      celsius = value - 273.15;
      break;
    default:
      celsius = value;
  }

  // 第二步:转换为目标单位
  switch (to) {
    case '°C':
      return celsius;
    case '°F':
      return celsius * 9 / 5 + 32;
    case 'K':
      return celsius + 273.15;
    default:
      return celsius;
  }
}

数值格式化

换算结果可能非常大或非常小,需要智能格式化以保证可读性:

dart 复制代码
String _formatNumber(double value) {
  if (value == 0) return '0';
  
  // 极大或极小的数使用科学计数法
  if (value.abs() >= 1e10 || (value.abs() < 1e-6 && value != 0)) {
    return value.toStringAsExponential(6);
  }
  
  // 整数直接显示
  if (value == value.roundToDouble()) {
    return value.toInt().toString();
  }
  
  // 小数去除末尾多余的0
  String str = value.toStringAsFixed(8);
  str = str.replaceAll(RegExp(r'0+$'), '');
  str = str.replaceAll(RegExp(r'\.$'), '');
  return str;
}

格式化规则:

数值范围 处理方式 示例
∣x∣≥1010|x| \geq 10^{10}∣x∣≥1010 科学计数法 1.234567e+12
∣x∣<10−6|x| < 10^{-6}∣x∣<10−6 科学计数法 1.234567e-8
整数 直接显示 1000
小数 去除末尾0 3.14

UI组件实现

类别选择器

横向滚动的类别选择器,每个类别用图标和名称展示,选中状态有明显的视觉反馈:

dart 复制代码
Widget _buildCategorySelector() {
  return Container(
    height: 100,
    padding: const EdgeInsets.symmetric(vertical: 8),
    child: ListView.builder(
      scrollDirection: Axis.horizontal,
      padding: const EdgeInsets.symmetric(horizontal: 12),
      itemCount: UnitData.categories.length,
      itemBuilder: (context, index) {
        final category = UnitData.categories[index];
        final isSelected = index == _selectedCategoryIndex;

        return Padding(
          padding: const EdgeInsets.symmetric(horizontal: 4),
          child: InkWell(
            onTap: () => setState(() => _selectedCategoryIndex = index),
            borderRadius: BorderRadius.circular(16),
            child: AnimatedContainer(
              duration: const Duration(milliseconds: 200),
              width: 80,
              padding: const EdgeInsets.all(8),
              decoration: BoxDecoration(
                color: isSelected
                    ? category.color.withValues(alpha: 0.2)
                    : Colors.grey.withValues(alpha: 0.1),
                borderRadius: BorderRadius.circular(16),
                border: Border.all(
                  color: isSelected ? category.color : Colors.transparent,
                  width: 2,
                ),
              ),
              child: Column(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  Icon(
                    category.icon,
                    color: isSelected ? category.color : Colors.grey,
                    size: 28,
                  ),
                  const SizedBox(height: 4),
                  Text(
                    category.name,
                    style: TextStyle(
                      fontSize: 12,
                      fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
                      color: isSelected ? category.color : Colors.grey,
                    ),
                  ),
                ],
              ),
            ),
          ),
        );
      },
    ),
  );
}

输入卡片

大字号输入框,配合清除按钮,提供舒适的输入体验:

dart 复制代码
Widget _buildInputCard(Color color) {
  return Card(
    elevation: 2,
    shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
    child: Padding(
      padding: const EdgeInsets.all(20),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text(
            '输入数值',
            style: TextStyle(fontSize: 14, color: Colors.grey[600]),
          ),
          const SizedBox(height: 12),
          TextField(
            controller: _inputController,
            keyboardType: const TextInputType.numberWithOptions(
              decimal: true, 
              signed: true,
            ),
            style: TextStyle(
              fontSize: 32,
              fontWeight: FontWeight.bold,
              color: color,
            ),
            decoration: InputDecoration(
              hintText: '0',
              border: InputBorder.none,
              suffixIcon: _inputController.text.isNotEmpty
                  ? IconButton(
                      icon: const Icon(Icons.clear),
                      onPressed: () => _inputController.clear(),
                    )
                  : null,
            ),
          ),
        ],
      ),
    ),
  );
}

单位选择器

点击单位区域弹出底部选择器,支持快速切换单位:

dart 复制代码
void _showUnitPicker(bool isFromUnit) {
  showModalBottomSheet(
    context: context,
    shape: const RoundedRectangleBorder(
      borderRadius: BorderRadius.vertical(top: Radius.circular(20)),
    ),
    builder: (context) {
      return Container(
        padding: const EdgeInsets.all(20),
        child: Column(
          mainAxisSize: MainAxisSize.min,
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(
              isFromUnit ? '选择源单位' : '选择目标单位',
              style: const TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 16),
            Flexible(
              child: ListView.builder(
                shrinkWrap: true,
                itemCount: widget.category.units.length,
                itemBuilder: (context, index) {
                  final unit = widget.category.units[index];
                  final isSelected = isFromUnit
                      ? index == _fromUnitIndex
                      : index == _toUnitIndex;

                  return ListTile(
                    title: Text('${unit.name} (${unit.symbol})'),
                    trailing: isSelected
                        ? Icon(Icons.check, color: widget.category.color)
                        : null,
                    selected: isSelected,
                    onTap: () {
                      setState(() {
                        if (isFromUnit) {
                          _fromUnitIndex = index;
                        } else {
                          _toUnitIndex = index;
                        }
                        _convert();
                      });
                      Navigator.pop(context);
                    },
                  );
                },
              ),
            ),
          ],
        ),
      );
    },
  );
}

全量结果展示

一次性展示输入值转换为所有其他单位的结果,方便对比:

dart 复制代码
void _calculateAllResults(double input) {
  final fromUnit = widget.category.units[_fromUnitIndex];
  _allResults = [];

  for (int i = 0; i < widget.category.units.length; i++) {
    if (i == _fromUnitIndex) continue;  // 跳过源单位

    final toUnit = widget.category.units[i];
    double result;

    if (widget.category.name == '温度') {
      result = _convertTemperature(input, fromUnit.symbol, toUnit.symbol);
    } else {
      final baseValue = input * fromUnit.toBase;
      result = baseValue / toUnit.toBase;
    }

    _allResults.add({
      'name': toUnit.name,
      'symbol': toUnit.symbol,
      'value': _formatNumber(result),
    });
  }
}

完整单位数据

应用支持的全部8大类别及其单位:
单位换算器
长度
千米/米/分米
厘米/毫米
英里/码/英尺/英寸
海里
面积
平方千米/公顷/亩
平方米/平方分米
平方厘米
平方英里/英亩/平方英尺
体积
立方米/升/毫升
立方厘米
加仑美/加仑英
品脱/盎司液
重量
吨/千克/克/毫克
磅/盎司
斤/两
温度
摄氏度
华氏度
开尔文
时间
年/月/周/天
小时/分钟/秒
毫秒
速度
米每秒/千米每时
英里每时/节
马赫/光速
数据
TB/GB/MB
KB/Byte/bit

数据存储单位说明

数据存储单位采用二进制标准(1KB = 1024B),而非十进制标准(1KB = 1000B):

单位 字节数 计算方式
TB 2402^{40}240 1,099,511,627,776 B
GB 2302^{30}230 1,073,741,824 B
MB 2202^{20}220 1,048,576 B
KB 2102^{10}210 1,024 B
Byte 202^020 1 B
bit 2−32^{-3}2−3 0.125 B
dart 复制代码
UnitCategory(
  name: '数据',
  icon: Icons.storage,
  color: Colors.pink,
  units: [
    UnitType(name: 'TB', symbol: 'TB', toBase: 1099511627776),
    UnitType(name: 'GB', symbol: 'GB', toBase: 1073741824),
    UnitType(name: 'MB', symbol: 'MB', toBase: 1048576),
    UnitType(name: 'KB', symbol: 'KB', toBase: 1024),
    UnitType(name: 'Byte', symbol: 'B', toBase: 1),
    UnitType(name: 'bit', symbol: 'bit', toBase: 0.125),
  ],
),

状态管理流程

视图 _calculateAllResults() _formatNumber() _convert() 输入框 用户 视图 _calculateAllResults() _formatNumber() _convert() 输入框 用户 alt [温度单位] [其他单位] 输入数值 TextEditingController监听 判断是否温度单位 _convertTemperature() 基准值换算 格式化结果 计算全部结果 格式化每个结果 setState() 更新显示

扩展建议

如果想进一步完善这个换算器,可以考虑:

  1. 历史记录:保存最近的换算记录,方便回顾
  2. 收藏功能:收藏常用的单位组合
  3. 货币换算:接入汇率API,支持实时货币转换
  4. 自定义单位:允许用户添加自定义单位
  5. 语音输入:支持语音输入数值

项目结构

复制代码
lib/
└── main.dart          # 完整应用代码
    ├── UnitCategory   # 单位类别模型
    ├── UnitType       # 单位类型模型
    ├── UnitData       # 单位数据定义
    ├── UnitConverterApp    # 主应用组件
    └── ConverterPage  # 换算页面组件

总结

这个单位换算器的实现展示了几个关键的编程思想:

  1. 数据驱动UI:通过定义清晰的数据模型,UI可以自动适应不同类别的单位
  2. 统一的换算接口:除温度外,所有单位都使用相同的换算公式,代码复用性高
  3. 特殊情况特殊处理:温度换算的非线性关系需要单独处理
  4. 智能数值格式化:根据数值大小自动选择合适的显示格式

整个应用代码量不大,但功能完备,适合作为Flutter工具类应用的学习案例。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

相关推荐
南村群童欺我老无力.2 小时前
Flutter 框架跨平台鸿蒙开发 - 井字棋游戏开发指南(含Minimax AI)
人工智能·flutter·华为·harmonyos
小白阿龙2 小时前
鸿蒙+Flutter 跨平台开发——围棋辅助教学APP
flutter·华为·harmonyos·鸿蒙
小白阿龙2 小时前
鸿蒙+flutter 跨平台开发——icon控件的响应式适配实现
flutter·华为·harmonyos·鸿蒙
2501_944525762 小时前
Flutter for OpenHarmony数独游戏App实战:笔记功能
笔记·flutter·游戏
2401_882351522 小时前
Flutter for OpenHarmony 商城App实战 - 积分实现
flutter
夜雨声烦丿2 小时前
Flutter 框架跨平台鸿蒙开发 - 打造生日/纪念日倒计时应用
flutter·华为·harmonyos
夜雨声烦丿2 小时前
Flutter 框架跨平台鸿蒙开发 - 打造功能完整的投票器应用
flutter·华为·harmonyos
酷酷的鱼2 小时前
2026 跨平台开发终极选型:Flutter, React Native 与 uni-app x 深度博标与规划指南
flutter·react native·uni-app
世人万千丶2 小时前
鸿蒙跨端框架 Flutter 学习 Day 4:网络交互——HTTP 请求基础与数据反序列化实战
网络·学习·flutter·ui·交互·harmonyos·鸿蒙