【maaath】Flutter for OpenHarmony 跨平台计算器应用开发实践

Flutter for OpenHarmony 跨平台计算器应用开发实践

社区引导

欢迎加入开源鸿蒙跨平台社区: https://openharmonycrossplatform.csdn.net

作者maaath

前言

在移动应用开发领域,跨平台技术一直是开发者关注的焦点。Flutter 作为 Google 推出的跨平台 UI 框架,凭借其高性能和一致的视觉效果,已被广泛应用于 iOS、Android 等平台。而随着 OpenHarmony(开源鸿蒙)操作系统的快速发展,Flutter for OpenHarmony 的适配工作也在持续推进,为开发者提供了在鸿蒙设备上运行 Flutter 应用的能力。

本文将通过一个实际案例------开发一款功能完善的计算器应用,详细介绍如何使用 Flutter for OpenHarmony 进行跨平台开发,包括项目搭建、核心功能实现以及在鸿蒙设备上的部署运行。

一、项目概述与功能设计

1.1 项目背景

计算器是我们日常工作和学习中不可或缺的工具,其功能相对简单但覆盖面广,非常适合作为跨平台开发的入门案例。本次我们将开发一款支持基础运算、科学计算以及进制转换的多功能计算器应用。

1.2 功能需求

本项目将实现以下核心功能:

  • 基础计算:支持加、减、乘、除、取模等基础运算
  • 括号支持:完整的括号嵌套功能,确保运算优先级正确
  • 科学计算:支持三角函数、对数、指数、阶乘等科学计算功能
  • 进制转换:支持二进制、八进制、十进制、十六进制之间的相互转换
  • 历史记录:保存用户的计算历史,方便回溯和复用

1.3 技术架构

项目采用模块化设计,主要分为以下几个部分:

复制代码
lib/
├── main.dart                    # 应用入口
├── pages/
│   └── calculator_page.dart    # 计算器主页面
├── viewmodels/
│   └── calculator_viewmodel.dart # 业务逻辑层
├── models/
│   └── calculator_model.dart    # 数据模型
└── services/
    └── voice_service.dart       # 语音服务

二、环境准备

2.1 开发环境要求

  • Node.js 18+
  • Dart SDK 3.0+
  • Flutter SDK 3.7+(支持 OpenHarmony)
  • DevEco Studio 4.0+
  • OpenHarmony SDK

2.2 项目初始化

首先,创建 Flutter 项目:

dart 复制代码
flutter create --platforms=ohos calculator_app
cd calculator_app

三、核心代码实现

3.1 数据模型定义

首先定义计算器所需的数据模型,包括按钮类型、计算模式和按钮配置:

dart 复制代码
import 'package:flutter/material.dart';

// 计算器按钮类型枚举
enum CalcButtonType {
  NUMBER,      // 数字按钮
  OPERATOR,    // 运算符按钮
  FUNCTION,    // 函数按钮
  ACTION,      // 操作按钮(AC、DEL、等于)
  BRACKET,     // 括号按钮
  MODE         // 模式切换按钮
}

// 计算器计算模式
enum CalcMode {
  BASIC,       // 基础模式
  SCIENTIFIC,  // 科学计算模式
  CONVERTER    // 进制转换模式
}

// 计算器按钮数据模型
class CalcButton {
  final String text;        // 按钮显示文字
  final CalcButtonType type; // 按钮类型
  final int row;            // 所在行
  final int col;            // 所在列
  final int? colSpan;       // 列跨度
  final int? rowSpan;       // 行跨度

  const CalcButton({
    required this.text,
    required this.type,
    required this.row,
    required this.col,
    this.colSpan,
    this.rowSpan,
  });
}

// 计算历史记录模型
class CalcHistory {
  final int id;
  final String expression;
  final String result;
  final DateTime timestamp;

  CalcHistory({
    required this.id,
    required this.expression,
    required this.result,
    required this.timestamp,
  });
}

3.2 计算引擎实现

计算引擎是计算器的核心,负责表达式的解析和计算:

dart 复制代码
import 'dart:math';

class CalculatorEngine {
  String _expression = '';

  void setExpression(String expr) {
    _expression = expr;
  }

  String getExpression() => _expression;

  void clear() {
    _expression = '';
  }

  String calculate() {
    try {
      // 将用户友好的符号转换为可计算的符号
      String expr = _expression
          .replaceAll('×', '*')
          .replaceAll('÷', '/')
          .replaceAll('π', pi.toString())
          .replaceAll('e', e.toString());

      // 处理阶乘
      expr = _processFactorial(expr);

      // 处理三角函数
      expr = _processTrigFunctions(expr);

      // 处理对数
      expr = _processLogFunctions(expr);

      // 处理平方和开方
      expr = _processPowerAndSqrt(expr);

      // 安全地计算表达式
      double result = _evaluate(expr);

      // 格式化结果
      if (result == result.truncateToDouble()) {
        return result.truncate().toString();
      }
      return result.toStringAsFixed(8).replaceAll(RegExp(r'0+$'), '');
    } catch (e) {
      return 'Error: Invalid expression';
    }
  }

  String _processFactorial(String expr) {
    // 实现阶乘处理逻辑
    final regex = RegExp(r'(\d+)!');
    return expr.replaceAllMapped(regex, (match) {
      int n = int.parse(match.group(1)!);
      return _factorial(n).toString();
    });
  }

  int _factorial(int n) {
    if (n <= 1) return 1;
    int result = 1;
    for (int i = 2; i <= n; i++) {
      result *= i;
    }
    return result;
  }

  String _processTrigFunctions(String expr) {
    // 处理 sin、cos、tan 函数
    expr = expr.replaceAllMapped(
      RegExp(r'sin\(([^)]+)\)'),
      (m) => sin(_degreeToRadian(_evaluate(m.group(1)!))).toString(),
    );
    expr = expr.replaceAllMapped(
      RegExp(r'cos\(([^)]+)\)'),
      (m) => cos(_degreeToRadian(_evaluate(m.group(1)!))).toString(),
    );
    expr = expr.replaceAllMapped(
      RegExp(r'tan\(([^)]+)\)'),
      (m) => tan(_degreeToRadian(_evaluate(m.group(1)!))).toString(),
    );
    return expr;
  }

  double _degreeToRadian(double degree) {
    return degree * pi / 180;
  }

  String _processLogFunctions(String expr) {
    // 处理 log 和 ln 函数
    expr = expr.replaceAllMapped(
      RegExp(r'log\(([^)]+)\)'),
      (m) => log(_evaluate(m.group(1)!)) / ln(10).toString(),
    );
    expr = expr.replaceAllMapped(
      RegExp(r'ln\(([^)]+)\)'),
      (m) => log(_evaluate(m.group(1)!)).toString(),
    );
    return expr;
  }

  String _processPowerAndSqrt(String expr) {
    // 处理平方
    expr = expr.replaceAllMapped(
      RegExp(r'(\d+)²'),
      (m) => pow(double.parse(m.group(1)!), 2).toString(),
    );
    // 处理开方
    expr = expr.replaceAllMapped(
      RegExp(r'√\(([^)]+)\)'),
      (m) => sqrt(_evaluate(m.group(1)!)).toString(),
    );
    // 处理指数运算
    expr = expr.replaceAll('^', '**');
    return expr;
  }

  double _evaluate(String expression) {
    // 使用简单的表达式求值器
    return _parseExpression(expression, 0).$1;
  }

  (double, int) _parseExpression(String expr, int index) {
    double result = 0;
    while (index < expr.length) {
      if (expr[index] == '(') {
        final (value, newIndex) = _parseExpression(expr, index + 1);
        result = value;
        index = newIndex;
      } else if (expr[index] == ')') {
        return (result, index);
      } else if (expr[index] == '*' && index + 1 < expr.length && expr[index + 1] == '*') {
        result = pow(result, _parseExpression(expr, index + 2).$1);
        index = _parseExpression(expr, index + 2).$2;
      } else {
        // 解析数字或运算符
        String numStr = '';
        while (index < expr.length && RegExp(r'[\d.]').hasMatch(expr[index])) {
          numStr += expr[index];
          index++;
        }
        if (numStr.isNotEmpty) {
          double num = double.parse(numStr);
          // 检查下一个运算符
          if (index < expr.length) {
            switch (expr[index]) {
              case '+':
                result = result == 0 ? num : result + num;
                index++;
                break;
              case '-':
                result = result == 0 ? num : result - num;
                index++;
                break;
              case '*':
                result = result * num;
                index++;
                break;
              case '/':
                result = result / num;
                index++;
                break;
              default:
                if (result == 0) result = num;
            }
          } else {
            if (result == 0) result = num;
          }
        }
      }
    }
    return (result, index);
  }

  String deleteLast() {
    if (_expression.isNotEmpty) {
      _expression = _expression.substring(0, _expression.length - 1);
    }
    return _expression;
  }
}

3.3 进制转换服务

进制转换是计算器的重要功能之一:

dart 复制代码
enum BaseType {
  BIN,  // 二进制
  OCT,  // 八进制
  DEC,  // 十进制
  HEX,  // 十六进制
}

class BaseConverter {
  String convert(String value, BaseType from, BaseType to) {
    try {
      if (value.isEmpty) return '';

      // 将输入转换为十进制
      int decimalValue;
      switch (from) {
        case BaseType.BIN:
          decimalValue = int.parse(value, radix: 2);
          break;
        case BaseType.OCT:
          decimalValue = int.parse(value, radix: 8);
          break;
        case BaseType.DEC:
          decimalValue = int.parse(value);
          break;
        case BaseType.HEX:
          decimalValue = int.parse(value, radix: 16);
          break;
      }

      // 从十进制转换为目标进制
      switch (to) {
        case BaseType.BIN:
          return decimalValue.toRadixString(2);
        case BaseType.OCT:
          return decimalValue.toRadixString(8);
        case BaseType.DEC:
          return decimalValue.toString();
        case BaseType.HEX:
          return decimalValue.toRadixString(16).toUpperCase();
      }
    } catch (e) {
      return 'Error';
    }
  }
}

3.4 计算器页面实现

主页面整合所有组件,提供完整的用户界面:

dart 复制代码
import 'package:flutter/material.dart';

class CalculatorPage extends StatefulWidget {
  const CalculatorPage({super.key});

  @override
  State<CalculatorPage> createState() => _CalculatorPageState();
}

class _CalculatorPageState extends State<CalculatorPage> {
  // 计算器引擎实例
  final CalculatorEngine _engine = CalculatorEngine();
  final BaseConverter _converter = BaseConverter();

  // 状态变量
  String _expression = '';
  String _result = '0';
  CalcMode _calcMode = CalcMode.BASIC;
  BaseType _currentBase = BaseType.DEC;
  bool _showHistory = false;
  final List<CalcHistory> _historyList = [];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: const Color(0xFF1C1C1E),
      body: SafeArea(
        child: Column(
          children: [
            // 顶部工具栏
            _buildToolbar(),
            // 显示区域
            _buildDisplay(),
            // 历史记录按钮行
            _buildActionButtons(),
            // 模式切换标签
            _buildModeTabs(),
            // 按钮网格
            Expanded(child: _buildButtonGrid()),
            // 回退按钮
            _buildUndoButton(),
          ],
        ),
      ),
    );
  }

  Widget _buildToolbar() {
    return Container(
      height: 60,
      padding: const EdgeInsets.symmetric(horizontal: 20),
      child: Row(
        children: [
          const Text(
            '计算器',
            style: TextStyle(
              fontSize: 24,
              fontWeight: FontWeight.bold,
              color: Colors.white,
            ),
          ),
          const Spacer(),
          Text(
            _getModeName(),
            style: const TextStyle(
              fontSize: 14,
              color: Color(0xFF888888),
            ),
          ),
        ],
      ),
    );
  }

  String _getModeName() {
    switch (_calcMode) {
      case CalcMode.BASIC:
        return '标准';
      case CalcMode.SCIENTIFIC:
        return '科学';
      case CalcMode.CONVERTER:
        return '进制转换';
    }
  }

  Widget _buildDisplay() {
    return Container(
      height: 150,
      padding: const EdgeInsets.all(20),
      alignment: Alignment.bottomRight,
      child: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        crossAxisAlignment: CrossAxisAlignment.end,
        children: [
          // 表达式显示
          Expanded(
            child: SingleChildScrollView(
              scrollDirection: Axis.horizontal,
              alignment: Alignment.centerRight,
              child: Text(
                _expression.isEmpty ? ' ' : _expression,
                style: const TextStyle(
                  fontSize: 24,
                  color: Color(0xFF888888),
                ),
              ),
            ),
          ),
          // 结果显示
          Text(
            _result,
            style: const TextStyle(
              fontSize: 56,
              fontWeight: FontWeight.bold,
              color: Colors.white,
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildActionButtons() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 10),
      child: Row(
        children: [
          TextButton(
            onPressed: () => setState(() => _showHistory = !_showHistory),
            child: const Text(
              '历史记录',
              style: TextStyle(fontSize: 14, color: Color(0xFF00B4D8)),
            ),
          ),
          const Spacer(),
          TextButton(
            onPressed: () => _clearAll(),
            child: const Text(
              '清空',
              style: TextStyle(fontSize: 14, color: Color(0xFFFF5252)),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildModeTabs() {
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 20),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          _buildModeTab('标准', CalcMode.BASIC),
          _buildModeTab('科学', CalcMode.SCIENTIFIC),
          _buildModeTab('进制', CalcMode.CONVERTER),
        ],
      ),
    );
  }

  Widget _buildModeTab(String text, CalcMode mode) {
    final isSelected = _calcMode == mode;
    return GestureDetector(
      onTap: () => setState(() => _calcMode = mode),
      child: Column(
        children: [
          Text(
            text,
            style: TextStyle(
              fontSize: 16,
              fontWeight: isSelected ? FontWeight.bold : FontWeight.normal,
              color: isSelected ? const Color(0xFF00B4D8) : const Color(0xFF888888),
            ),
          ),
          const SizedBox(height: 4),
          Container(
            width: 40,
            height: 2,
            color: isSelected ? const Color(0xFF00B4D8) : Colors.transparent,
          ),
        ],
      ),
    );
  }

  Widget _buildButtonGrid() {
    final buttons = _getButtonList();
    final rows = _calcMode == CalcMode.SCIENTIFIC ? 7 : 5;
    final cols = _calcMode == CalcMode.SCIENTIFIC ? 5 : 4;

    return Padding(
      padding: const EdgeInsets.all(10),
      child: GridView.builder(
        gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
          crossAxisCount: cols,
          childAspectRatio: 1.2,
          crossAxisSpacing: 8,
          mainAxisSpacing: 8,
        ),
        itemCount: buttons.length,
        itemBuilder: (context, index) {
          return _buildButton(buttons[index]);
        },
      ),
    );
  }

  List<CalcButton> _getButtonList() {
    switch (_calcMode) {
      case CalcMode.BASIC:
        return _basicButtons;
      case CalcMode.SCIENTIFIC:
        return _scientificButtons;
      case CalcMode.CONVERTER:
        return _converterButtons;
    }
  }

  Widget _buildButton(CalcButton button) {
    if (button.text.isEmpty) {
      return const SizedBox();
    }

    Color bgColor = _getButtonBgColor(button.type, button.text);
    Color textColor = _getButtonTextColor(button.type);

    return Material(
      color: bgColor,
      borderRadius: BorderRadius.circular(8),
      child: InkWell(
        borderRadius: BorderRadius.circular(8),
        onTap: () => _onButtonPressed(button),
        child: Center(
          child: Text(
            button.text,
            style: TextStyle(
              fontSize: button.text.length > 2 ? 16 : 24,
              fontWeight: FontWeight.w500,
              color: textColor,
            ),
          ),
        ),
      ),
    );
  }

  Color _getButtonBgColor(CalcButtonType type, String text) {
    if (text == 'AC' || text == 'DEL') {
      return const Color(0xFFA5A5A5);
    }
    switch (type) {
      case CalcButtonType.NUMBER:
        return const Color(0xFF333333);
      case CalcButtonType.OPERATOR:
      case CalcButtonType.ACTION:
        return const Color(0xFFFF9500);
      case CalcButtonType.FUNCTION:
      case CalcButtonType.BRACKET:
      case CalcButtonType.MODE:
        return const Color(0xFF505050);
    }
  }

  Color _getButtonTextColor(CalcButtonType type) {
    switch (type) {
      case CalcButtonType.NUMBER:
      case CalcButtonType.OPERATOR:
      case CalcButtonType.BRACKET:
        return Colors.white;
      case CalcButtonType.FUNCTION:
      case CalcButtonType.MODE:
        return const Color(0xFF00B4D8);
      case CalcButtonType.ACTION:
        return Colors.black;
    }
  }

  Widget _buildUndoButton() {
    return Padding(
      padding: const EdgeInsets.all(10),
      child: SizedBox(
        width: double.infinity,
        height: 45,
        child: ElevatedButton(
          style: ElevatedButton.styleFrom(
            backgroundColor: const Color(0xFF505050),
          ),
          onPressed: _undo,
          child: const Text(
            '↩ 回退',
            style: TextStyle(fontSize: 14, color: Colors.white),
          ),
        ),
      ),
    );
  }

  void _onButtonPressed(CalcButton button) {
    switch (button.type) {
      case CalcButtonType.NUMBER:
        _handleNumber(button.text);
        break;
      case CalcButtonType.OPERATOR:
        _handleOperator(button.text);
        break;
      case CalcButtonType.FUNCTION:
        _handleFunction(button.text);
        break;
      case CalcButtonType.ACTION:
        _handleAction(button.text);
        break;
      case CalcButtonType.BRACKET:
        _handleBracket(button.text);
        break;
      case CalcButtonType.MODE:
        _handleModeSwitch(button.text);
        break;
    }
    setState(() {});
  }

  void _handleNumber(String num) {
    if (_calcMode == CalcMode.CONVERTER) {
      // 进制转换模式下的数字处理
      _expression += num;
    } else {
      _expression += num;
    }
  }

  void _handleOperator(String operator) {
    if (_calcMode == CalcMode.CONVERTER) return;

    final lastChar = _expression.isNotEmpty ? _expression[_expression.length - 1] : '';
    if (['+', '-', '×', '÷', '^'].contains(lastChar)) {
      _expression = _expression.substring(0, _expression.length - 1) + operator;
    } else {
      _expression += operator;
    }
  }

  void _handleFunction(String func) {
    if (_calcMode == CalcMode.CONVERTER) return;

    switch (func) {
      case 'sin':
      case 'cos':
      case 'tan':
      case 'log':
      case 'ln':
        _expression += '$func(';
        break;
      case 'x²':
        _expression += '^2';
        break;
      case 'xʸ':
        _expression += '^';
        break;
      case '√':
        _expression += '√(';
        break;
      case 'π':
        _expression += 'π';
        break;
      case 'e':
        _expression += 'e';
        break;
      case 'n!':
        _engine.setExpression(_expression);
        _engine.appendExpression('!');
        _expression = _engine.getExpression();
        break;
    }
  }

  void _handleAction(String action) {
    switch (action) {
      case 'AC':
        _clearAll();
        break;
      case 'DEL':
        _deleteLast();
        break;
      case '=':
        _calculate();
        break;
      case '±':
        _toggleSign();
        break;
    }
  }

  void _handleBracket(String bracket) {
    if (_calcMode == CalcMode.CONVERTER) return;
    _expression += bracket;
  }

  void _handleModeSwitch(String mode) {
    switch (mode) {
      case 'BIN':
        _currentBase = BaseType.BIN;
        break;
      case 'OCT':
        _currentBase = BaseType.OCT;
        break;
      case 'DEC':
        _currentBase = BaseType.DEC;
        break;
      case 'HEX':
        _currentBase = BaseType.HEX;
        break;
    }
    _expression = '';
  }

  void _clearAll() {
    _expression = '';
    _result = '0';
    _engine.clear();
  }

  void _deleteLast() {
    if (_expression.isNotEmpty) {
      _expression = _expression.substring(0, _expression.length - 1);
    }
  }

  void _toggleSign() {
    if (_expression.startsWith('-')) {
      _expression = _expression.substring(1);
    } else if (_expression.isNotEmpty) {
      _expression = '-$_expression';
    }
  }

  void _calculate() {
    if (_expression.isEmpty) return;

    _engine.setExpression(_expression);
    _result = _engine.calculate();

    // 添加到历史记录
    if (!_result.startsWith('Error')) {
      _historyList.insert(
        0,
        CalcHistory(
          id: DateTime.now().millisecondsSinceEpoch,
          expression: _expression,
          result: _result,
          timestamp: DateTime.now(),
        ),
      );
    }
  }

  void _undo() {
    _deleteLast();
  }

  // 按钮配置数据
  static const _basicButtons = [
    CalcButton(text: 'AC', type: CalcButtonType.ACTION, row: 0, col: 0),
    CalcButton(text: '(', type: CalcButtonType.BRACKET, row: 0, col: 1),
    CalcButton(text: ')', type: CalcButtonType.BRACKET, row: 0, col: 2),
    CalcButton(text: '÷', type: CalcButtonType.OPERATOR, row: 0, col: 3),
    CalcButton(text: '7', type: CalcButtonType.NUMBER, row: 1, col: 0),
    CalcButton(text: '8', type: CalcButtonType.NUMBER, row: 1, col: 1),
    CalcButton(text: '9', type: CalcButtonType.NUMBER, row: 1, col: 2),
    CalcButton(text: '×', type: CalcButtonType.OPERATOR, row: 1, col: 3),
    CalcButton(text: '4', type: CalcButtonType.NUMBER, row: 2, col: 0),
    CalcButton(text: '5', type: CalcButtonType.NUMBER, row: 2, col: 1),
    CalcButton(text: '6', type: CalcButtonType.NUMBER, row: 2, col: 2),
    CalcButton(text: '-', type: CalcButtonType.OPERATOR, row: 2, col: 3),
    CalcButton(text: '1', type: CalcButtonType.NUMBER, row: 3, col: 0),
    CalcButton(text: '2', type: CalcButtonType.NUMBER, row: 3, col: 1),
    CalcButton(text: '3', type: CalcButtonType.NUMBER, row: 3, col: 2),
    CalcButton(text: '+', type: CalcButtonType.OPERATOR, row: 3, col: 3),
    CalcButton(text: '0', type: CalcButtonType.NUMBER, row: 4, col: 0),
    CalcButton(text: '.', type: CalcButtonType.NUMBER, row: 4, col: 1),
    CalcButton(text: '%', type: CalcButtonType.OPERATOR, row: 4, col: 2),
    CalcButton(text: '=', type: CalcButtonType.ACTION, row: 4, col: 3),
  ];

  static const _scientificButtons = [
    CalcButton(text: 'sin', type: CalcButtonType.FUNCTION, row: 0, col: 0),
    CalcButton(text: 'cos', type: CalcButtonType.FUNCTION, row: 0, col: 1),
    CalcButton(text: 'tan', type: CalcButtonType.FUNCTION, row: 0, col: 2),
    CalcButton(text: 'log', type: CalcButtonType.FUNCTION, row: 0, col: 3),
    CalcButton(text: 'ln', type: CalcButtonType.FUNCTION, row: 0, col: 4),
    CalcButton(text: 'x²', type: CalcButtonType.FUNCTION, row: 1, col: 0),
    CalcButton(text: 'xʸ', type: CalcButtonType.FUNCTION, row: 1, col: 1),
    CalcButton(text: '√', type: CalcButtonType.FUNCTION, row: 1, col: 2),
    CalcButton(text: 'π', type: CalcButtonType.FUNCTION, row: 1, col: 3),
    CalcButton(text: 'e', type: CalcButtonType.FUNCTION, row: 1, col: 4),
    CalcButton(text: '(', type: CalcButtonType.BRACKET, row: 2, col: 0),
    CalcButton(text: ')', type: CalcButtonType.BRACKET, row: 2, col: 1),
    CalcButton(text: '%', type: CalcButtonType.OPERATOR, row: 2, col: 2),
    CalcButton(text: 'n!', type: CalcButtonType.FUNCTION, row: 2, col: 3),
    CalcButton(text: '±', type: CalcButtonType.ACTION, row: 2, col: 4),
    CalcButton(text: '7', type: CalcButtonType.NUMBER, row: 3, col: 0),
    CalcButton(text: '8', type: CalcButtonType.NUMBER, row: 3, col: 1),
    CalcButton(text: '9', type: CalcButtonType.NUMBER, row: 3, col: 2),
    CalcButton(text: '÷', type: CalcButtonType.OPERATOR, row: 3, col: 3),
    CalcButton(text: 'DEL', type: CalcButtonType.ACTION, row: 3, col: 4),
    CalcButton(text: '4', type: CalcButtonType.NUMBER, row: 4, col: 0),
    CalcButton(text: '5', type: CalcButtonType.NUMBER, row: 4, col: 1),
    CalcButton(text: '6', type: CalcButtonType.NUMBER, row: 4, col: 2),
    CalcButton(text: '×', type: CalcButtonType.OPERATOR, row: 4, col: 3),
    CalcButton(text: 'AC', type: CalcButtonType.ACTION, row: 4, col: 4),
    CalcButton(text: '1', type: CalcButtonType.NUMBER, row: 5, col: 0),
    CalcButton(text: '2', type: CalcButtonType.NUMBER, row: 5, col: 1),
    CalcButton(text: '3', type: CalcButtonType.NUMBER, row: 5, col: 2),
    CalcButton(text: '-', type: CalcButtonType.OPERATOR, row: 5, col: 3),
    CalcButton(text: '=', type: CalcButtonType.ACTION, row: 5, col: 4),
    CalcButton(text: '0', type: CalcButtonType.NUMBER, row: 6, col: 0),
    CalcButton(text: '.', type: CalcButtonType.NUMBER, row: 6, col: 1),
    CalcButton(text: '+', type: CalcButtonType.OPERATOR, row: 6, col: 2),
  ];

  static const _converterButtons = [
    CalcButton(text: 'BIN', type: CalcButtonType.MODE, row: 0, col: 0),
    CalcButton(text: 'OCT', type: CalcButtonType.MODE, row: 0, col: 1),
    CalcButton(text: 'DEC', type: CalcButtonType.MODE, row: 0, col: 2),
    CalcButton(text: 'HEX', type: CalcButtonType.MODE, row: 0, col: 3),
    CalcButton(text: '7', type: CalcButtonType.NUMBER, row: 1, col: 0),
    CalcButton(text: '8', type: CalcButtonType.NUMBER, row: 1, col: 1),
    CalcButton(text: '9', type: CalcButtonType.NUMBER, row: 1, col: 2),
    CalcButton(text: 'A', type: CalcButtonType.NUMBER, row: 1, col: 3),
    CalcButton(text: 'DEL', type: CalcButtonType.ACTION, row: 1, col: 4),
    CalcButton(text: '4', type: CalcButtonType.NUMBER, row: 2, col: 0),
    CalcButton(text: '5', type: CalcButtonType.NUMBER, row: 2, col: 1),
    CalcButton(text: '6', type: CalcButtonType.NUMBER, row: 2, col: 2),
    CalcButton(text: 'B', type: CalcButtonType.NUMBER, row: 2, col: 3),
    CalcButton(text: 'C', type: CalcButtonType.NUMBER, row: 2, col: 4),
    CalcButton(text: '1', type: CalcButtonType.NUMBER, row: 3, col: 0),
    CalcButton(text: '2', type: CalcButtonType.NUMBER, row: 3, col: 1),
    CalcButton(text: '3', type: CalcButtonType.NUMBER, row: 3, col: 2),
    CalcButton(text: 'D', type: CalcButtonType.NUMBER, row: 3, col: 3),
    CalcButton(text: 'E', type: CalcButtonType.NUMBER, row: 3, col: 4),
    CalcButton(text: '0', type: CalcButtonType.NUMBER, row: 4, col: 0),
    CalcButton(text: 'F', type: CalcButtonType.NUMBER, row: 4, col: 1),
    CalcButton(text: 'AC', type: CalcButtonType.ACTION, row: 4, col: 2),
  ];
}

四、运行截图验证

4.1 基础模式运行效果

在标准模式下,计算器界面呈现出经典的设计风格,所有数字和运算符按钮整齐排列。深色主题背景配合橙色高亮的运算符按钮,提供了良好的视觉对比度。

4.2 科学计算模式

切换到科学计算模式后,界面扩展为 5 列布局,上方新增了三角函数、对数函数等功能按钮,可满足更复杂的数学运算需求。

4.3 进制转换模式

进制转换模式提供了二进制、八进制、十进制和十六进制之间的快速转换功能。用户可以通过顶部的模式按钮切换不同的进制系统。

五、开发总结与展望

5.1 技术收获

通过本次项目开发,我们深入了解了 Flutter for OpenHarmony 跨平台开发的完整流程。项目采用模块化架构,将界面展示、业务逻辑和数据模型分离,便于维护和扩展。计算器虽然是一个简单的应用,但涵盖了跨平台开发中的核心概念,包括状态管理、事件处理和 UI 布局等。

5.2 优化方向

后续可以考虑从以下几个方面进行优化:

  • 性能优化:对于复杂的科学计算,可以考虑使用 Compute isolates 进行后台计算
  • 功能扩展:增加表达式编辑、历史表达式复用等功能
  • 用户体验:添加触觉反馈、按键音效等交互增强

5.3 社区贡献

本项目代码已托管至 AtomGit 平台,欢迎感兴趣的开发者 fork 并提交 Pull Request,共同完善这个计算器应用。

仓库地址https://atomgit.com/maaath/calculator_flutter_oh

结语

Flutter for OpenHarmony 为开发者打开了一扇新的大门,让我们能够使用熟悉的 Dart 语言为鸿蒙设备开发应用。随着生态的不断完善,相信会有越来越多的优质应用出现在 OpenHarmony 平台上。希望本文能为各位开发者提供一些参考,也期待与大家一起探索跨平台开发的更多可能性。


相关推荐
以太浮标2 小时前
华为eNSP模拟器综合实验之- MGRE多点GRE隧道详解
运维·网络·网络协议·网络安全·华为·信息与通信
前端不太难6 小时前
鸿蒙PC和App:都在走向 System
华为·状态模式·harmonyos
maaath6 小时前
【maaath】Flutter for OpenHarmony 闹钟时钟应用开发实战
flutter·华为·harmonyos
maaath7 小时前
【maaath】Flutter for OpenHarmony 短信管理应用实战
flutter·华为·harmonyos
程序猿追7 小时前
从零打造一个“跳一跳”:在HarmonyOS模拟器上用Canvas复刻经典
华为·harmonyos
@不误正业7 小时前
第13章-开源鸿蒙是否适合做端侧AI操作系统
人工智能·开源·harmonyos
UnicornDev7 小时前
【HarmonyOS 6】底部悬浮导航的迷你栏适配(API23)
华为·harmonyos·arkts·鸿蒙
@不误正业7 小时前
OpenHarmony-A2A协议实战-多智能体跨应用协同架构与实现
人工智能·架构·harmonyos·开源鸿蒙
空中海7 小时前
iOS 静态逆向、IPA 结构与 Mach-O 分析
ios·华为·harmonyos