Flutter for OpenHarmony 实战:科学计算器完整开发指南

摘要

在这里插入图片描述

计算器是移动设备中最基础且最重要的工具应用之一。本文将详细介绍如何使用Flutter for OpenHarmony框架开发一款功能完整的计算器应用。文章涵盖了计算器UI设计、运算逻辑实现、状态管理、错误处理等核心技术点。通过本文学习,读者将掌握Flutter在鸿蒙平台上开发工具类应用的完整流程,了解如何构建功能完善、用户体验优秀的计算器应用。


一、项目背景与功能概述

1.1 计算器应用的历史

计算器应用从最初的简单加减乘除发展到如今支持科学运算、单位转换等多种功能。随着智能手机的普及,计算器成为每台设备必备的基础应用。

1.2 应用功能规划

功能模块 具体功能
基本运算 加、减、乘、除、取余
数字输入 0-9数字、小数点、正负号切换
功能操作 清除、删除
显示功能 表达式显示、结果显示
错误处理 除零检测、格式错误处理

1.3 为什么选择Flutter for OpenHarmony

Flutter在开发计算器应用时具有明显优势:

精确的数值计算

  • double类型提供高精度计算
  • 避免浮点数精度问题
  • 支持大数运算

流畅的交互体验

  • 即时响应的按钮反馈
  • 平滑的状态更新
  • 自适应的布局设计

跨平台一致性

  • 一套代码多端运行
  • 统一的按键手感
  • 降低维护成本

二、计算器设计原则

2.1 用户界面设计

布局原则

  • 显示区域位于顶部
  • 按钮区域占据主要空间
  • 操作符与数字颜色区分
  • 等号按钮突出显示

交互原则

  • 即时响应按钮点击
  • 清晰的状态反馈
  • 错误提示友好明确
  • 支持连续运算

2.2 计算逻辑设计

运算流程
在这里插入图片描述

状态管理

  • 当前显示值
  • 第一个操作数
  • 当前运算符
  • 表达式显示

三、技术选型与架构设计

3.1 核心技术栈

UI组件

  • Column:垂直布局
  • Row:水平布局
  • ElevatedButton:按钮组件
  • Text:文本显示

状态管理

  • StatefulWidget管理状态
  • setState更新UI
  • 成员变量存储状态

数据类型

  • double:数值计算
  • String:显示处理
  • enum:按钮类型定义

3.2 应用架构

CalculatorApp (应用根组件)
    └── CalculatorPage (计算器页面)
        ├── AppBar (导航栏)
        ├── DisplayArea (显示区域)
        │   ├── 表达式显示
        │   └── 结果显示
        └── ButtonGrid (按钮网格)
            ├── 第1行: C, ⌫, %, ÷
            ├── 第2行: 7, 8, 9, ×
            ├── 第3行: 4, 5, 6, -
            ├── 第4行: 1, 2, 3, +
            └── 第5行: 0, ., ±, =

3.3 按钮类型设计

使用枚举定义按钮类型:

enum CalculatorButtonType {
  number,     // 数字按钮:0-9, 小数点
  operator,   // 运算符按钮:+, -, ×, ÷, %
  function,   // 功能按钮:清除, 删除, 正负号
  equals,     // 等号按钮
}

四、数据模型设计

4.1 按钮数据模型

class CalculatorButton {
  final String label;      // 按钮显示文本
  final CalculatorButtonType type; // 按钮类型
  final String? value;     // 按钮值(用于计算)

  const CalculatorButton({
    required this.label,
    required this.type,
    this.value,
  });
}

设计要点

  • label:用户看到的按钮文字
  • type:按钮的类型分类
  • value:实际参与计算的值

4.2 状态变量设计

class _CalculatorPageState extends State<CalculatorPage> {
  String _display = '0';          // 当前显示的值
  String _expression = '';        // 表达式显示
  double? _firstOperand;          // 第一个操作数
  String? _operator;              // 当前运算符
  bool _resetDisplay = false;     // 是否重置显示
  bool _hasError = false;         // 是否有错误
}

变量说明

  • _display:主显示区域的内容
  • _expression:上方显示的运算过程
  • _firstOperand:存储的第一个操作数
  • _operator:当前选中的运算符
  • _resetDisplay:控制输入时是否清空当前显示
  • _hasError:标识是否处于错误状态

五、UI界面实现

5.1 按钮布局定义

使用二维数组定义按钮布局:
在这里插入图片描述

static const List<List<CalculatorButton>> _buttons = [
  [
    CalculatorButton(label: 'C', type: CalculatorButtonType.function, value: 'clear'),
    CalculatorButton(label: '⌫', type: CalculatorButtonType.function, value: 'delete'),
    CalculatorButton(label: '%', type: CalculatorButtonType.operator, value: '%'),
    CalculatorButton(label: '÷', type: CalculatorButtonType.operator, value: '/'),
  ],
  [
    CalculatorButton(label: '7', type: CalculatorButtonType.number, value: '7'),
    CalculatorButton(label: '8', type: CalculatorButtonType.number, value: '8'),
    CalculatorButton(label: '9', type: CalculatorButtonType.number, value: '9'),
    CalculatorButton(label: '×', type: CalculatorButtonType.operator, value: '*'),
  ],
  // ... 其他行
];

5.2 显示区域实现

Widget _buildDisplayArea() {
  return Container(
    padding: const EdgeInsets.all(24),
    color: Colors.grey[100],
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.end,
      children: [
        // 表达式显示
        if (_expression.isNotEmpty)
          Text(
            _expression,
            style: TextStyle(
              fontSize: 20,
              color: Colors.grey[600],
            ),
          ),
        const SizedBox(height: 8),
        // 主显示
        Text(
          _display,
          style: TextStyle(
            fontSize: 48,
            fontWeight: FontWeight.bold,
            color: _hasError ? Colors.red : Colors.black,
          ),
          textAlign: TextAlign.right,
          maxLines: 1,
          overflow: TextOverflow.ellipsis,
        ),
      ],
    ),
  );
}

设计要点

  • 右对齐显示数字
  • 表达式使用较小字体和灰色
  • 主显示使用大字体和粗体
  • 错误时文字变红
  • 超长时省略显示

5.3 按钮网格实现

Widget _buildButtonGrid() {
  return Container(
    color: Colors.grey[200],
    child: Column(
      children: _buttons.map((row) {
        return Expanded(
          child: Row(
            children: row.map((button) {
              return Expanded(
                child: _buildButton(button),
              );
            }).toList(),
          ),
        );
      }).toList(),
    ),
  );
}

5.4 单个按钮实现

Widget _buildButton(CalculatorButton button) {
  return Container(
    margin: const EdgeInsets.all(4),
    child: ElevatedButton(
      onPressed: () => _onButtonPressed(button),
      style: ElevatedButton.styleFrom(
        backgroundColor: _getButtonColor(button.type),
        foregroundColor: _getButtonTextColor(button.type),
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.circular(8),
        ),
        elevation: 2,
      ),
      child: Text(
        button.label,
        style: const TextStyle(
          fontSize: 24,
          fontWeight: FontWeight.w500,
        ),
      ),
    ),
  );
}

5.5 按钮颜色方案

Color _getButtonColor(CalculatorButtonType type) {
  switch (type) {
    case CalculatorButtonType.number:
      return Colors.grey[300]!;
    case CalculatorButtonType.operator:
      return Colors.orange;
    case CalculatorButtonType.function:
      return Colors.grey;
    case CalculatorButtonType.equals:
      return Colors.blue;
  }
}

Color _getButtonTextColor(CalculatorButtonType type) {
  switch (type) {
    case CalculatorButtonType.number:
      return Colors.black;
    case CalculatorButtonType.operator:
    case CalculatorButtonType.equals:
      return Colors.white;
    case CalculatorButtonType.function:
      return Colors.black;
  }
}

颜色设计

  • 数字按钮:浅灰色背景,黑色文字
  • 运算符:橙色背景,白色文字
  • 功能按钮:灰色背景,黑色文字
  • 等号按钮:蓝色背景,白色文字

六、运算逻辑实现

6.1 按钮点击处理

void _onButtonPressed(CalculatorButton button) {
  setState(() {
    _hasError = false;

    switch (button.type) {
      case CalculatorButtonType.number:
        _handleNumberButton(button.value!);
        break;
      case CalculatorButtonType.operator:
        _handleOperatorButton(button.value!);
        break;
      case CalculatorButtonType.function:
        _handleFunctionButton(button.value!);
        break;
      case CalculatorButtonType.equals:
        _handleEqualsButton();
        break;
    }
  });
}

6.2 数字输入处理

void _handleNumberButton(String value) {
  if (_resetDisplay) {
    _display = '0';
    _resetDisplay = false;
  }

  if (value == '.') {
    // 小数点处理:只允许一个小数点
    if (!_display.contains('.')) {
      _display = _display == '0' ? '0.' : '$_display.';
    }
  } else {
    // 数字处理:0开头时替换,否则追加
    if (_display == '0') {
      _display = value;
    } else {
      _display += value;
    }
  }
}

输入规则

  • 小数点只能输入一次
  • 0后输入数字替换0
  • 其他情况追加到末尾

6.3 运算符处理

void _handleOperatorButton(String value) {
  if (_firstOperand == null) {
    // 第一次输入运算符,保存当前值
    _firstOperand = double.tryParse(_display);
    if (_firstOperand == null) {
      _hasError = true;
      _display = '错误';
      return;
    }
  } else if (!_resetDisplay && _operator != null) {
    // 连续运算,先计算之前的结果
    _calculateResult();
  }

  _operator = value;
  _resetDisplay = true;
  _expression = '$_display $value';
}

运算逻辑

  • 首次输入运算符:保存第一个操作数
  • 连续运算:先计算之前的表达式
  • 更新运算符和表达式显示

6.4 功能按钮处理

void _handleFunctionButton(String value) {
  switch (value) {
    case 'clear':
      _clearAll();
      break;
    case 'delete':
      _deleteLastChar();
      break;
    case 'negate':
      _negateNumber();
      break;
  }
}

// 清除所有
void _clearAll() {
  _display = '0';
  _expression = '';
  _firstOperand = null;
  _operator = null;
  _resetDisplay = false;
}

// 删除最后一个字符
void _deleteLastChar() {
  if (_display.length > 1) {
    _display = _display.substring(0, _display.length - 1);
  } else {
    _display = '0';
  }
}

// 取反数字
void _negateNumber() {
  final number = double.tryParse(_display);
  if (number != null) {
    _display = (-number).toString();
  }
}

6.5 等号处理

void _handleEqualsButton() {
  if (_firstOperand != null && _operator != null) {
    _calculateResult();
    _expression = '';
    _operator = null;
    _firstOperand = null;
    _resetDisplay = true;
  }
}

6.6 计算结果

void _calculateResult() {
  final secondOperand = double.tryParse(_display);
  if (secondOperand == null) {
    _hasError = true;
    _display = '错误';
    return;
  }

  double result;
  try {
    result = _performOperation(_firstOperand!, secondOperand, _operator!);
  } catch (e) {
    _hasError = true;
    _display = '错误';
    return;
  }

  // 处理除数为零的情况
  if (!result.isFinite) {
    _hasError = true;
    _display = '不能除以零';
    return;
  }

  // 格式化结果
  _display = _formatResult(result);
  _firstOperand = result;
}

在这里插入图片描述

6.7 运算执行

double _performOperation(double a, double b, String operator) {
  switch (operator) {
    case '+':
      return a + b;
    case '-':
      return a - b;
    case '*':
      return a * b;
    case '/':
      return a / b;
    case '%':
      return a % b;
    default:
      throw ArgumentError('未知的运算符: $operator');
  }
}

6.8 结果格式化

String _formatResult(double result) {
  // 整数直接显示
  if (result == result.truncateToDouble()) {
    return result.toInt().toString();
  }

  // 处理浮点数精度问题
  String formatted = result.toString();
  if (formatted.contains('.') && formatted.length > 12) {
    // 使用科学计数法或四舍五入
    if (result.abs() >= 1e10 || result.abs() < 1e-6 && result != 0) {
      return result.toStringAsExponential(6);
    } else {
      // 保留最多10位小数
      final rounded = double.parse(result.toStringAsFixed(10));
      return rounded.toString();
    }
  }

  return formatted;
}

格式化策略

  • 整数不显示小数点
  • 超大或超小数使用科学计数法
  • 长小数进行四舍五入

七、状态管理

7.1 状态变量管理

使用StatefulWidget管理所有状态:

class _CalculatorPageState extends State<CalculatorPage> {
  // 状态变量
  String _display = '0';
  String _expression = '';
  double? _firstOperand;
  String? _operator;
  bool _resetDisplay = false;
  bool _hasError = false;

  // 状态更新
  void _updateDisplay(String value) {
    setState(() {
      _display = value;
    });
  }
}

7.2 状态转换图

初始状态 (display='0')
    ↓ [输入数字]
数字输入状态
    ↓ [输入运算符]
运算符选中状态
    ↓ [输入数字]
第二操作数状态
    ↓ [按等号]
结果显示状态
    ↓ [输入数字]
回到数字输入状态

7.3 错误状态处理

// 检测错误状态
if (_firstOperand == null) {
  _hasError = true;
  _display = '错误';
  return;
}

// 显示错误
if (_hasError) {
  Text(
    _display,
    style: TextStyle(
      fontSize: 48,
      fontWeight: FontWeight.bold,
      color: Colors.red, // 红色显示错误
    ),
  );
}

// 清除错误状态
setState(() {
  _hasError = false;
});

八、完整代码实现

8.1 应用入口

void main() {
  runApp(const CalculatorApp());
}

class CalculatorApp extends StatelessWidget {
  const CalculatorApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: '计算器',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
        useMaterial3: true,
      ),
      home: const CalculatorPage(),
    );
  }
}

8.2 主页面结构


Widget build(BuildContext context) {
  return Scaffold(
    appBar: AppBar(
      title: const Text('计算器'),
      backgroundColor: Theme.of(context).colorScheme.inversePrimary,
    ),
    body: Column(
      children: [
        // 显示区域
        _buildDisplayArea(),
        const Divider(height: 1),
        // 按钮区域
        Expanded(
          child: _buildButtonGrid(),
        ),
      ],
    ),
  );
}

九、运行效果与测试

9.1 项目运行命令

cd E:\HarmonyOS\oh.code\calculator
flutter run -d ohos

9.2 功能测试清单

基本运算测试

  • 加法:2 + 3 = 5
  • 减法:10 - 4 = 6
  • 乘法:7 × 8 = 56
  • 除法:15 ÷ 3 = 5
  • 取余:17 % 5 = 2

小数运算测试

  • 小数加法:1.5 + 2.3 = 3.8
  • 小数乘法:0.5 × 0.2 = 0.1

连续运算测试

  • 连续加法:5 + 3 + 2 = 10
  • 混合运算:10 - 3 + 5 = 12

功能按钮测试

  • 清除按钮:重置所有状态
  • 删除按钮:逐个删除字符
  • 正负号:切换数字符号

错误处理测试

  • 除以零:显示"不能除以零"
  • 无效输入:显示"错误"

9.3 边界条件测试

大数运算

  • 9999999999 + 1 = 科学计数法显示
  • 0.000000001 × 0.000000001 = 科学计数法显示

特殊输入

  • 连续输入小数点:只允许一个
  • 0开头输入:正确处理
  • 超长数字:正确显示

十、总结

本文详细介绍了使用Flutter for OpenHarmony开发计算器应用的完整过程,涵盖了以下核心技术点:

  1. UI布局设计:Column、Row实现网格布局
  2. 按钮系统:enum分类、统一样式、颜色区分
  3. 运算逻辑:状态管理、连续运算、错误处理
  4. 格式化显示:整数、小数、科学计数法
  5. 状态管理:setState更新、条件渲染

这个项目展示了Flutter在工具类应用开发中的完整流程,代码结构清晰,功能完整。读者可以基于此项目添加更多功能,如:

  • 科学计算功能(三角函数、对数等)
  • 历史记录功能
  • 单位转换功能
  • 复制粘贴功能
  • 语音输入功能

通过本文的学习,读者应该能够独立开发类似的工具类应用,掌握Flutter在鸿蒙平台上的UI设计和状态管理技巧。


欢迎加入开源鸿蒙跨平台社区: 开源鸿蒙跨平台开发者社区

Logo

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

更多推荐