Flutter for OpenHarmony 跨平台开发:计算器功能实战指南

Flutter for OpenHarmony 跨平台开发:计算器功能实战指南

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


一、引言

嘿,亲爱的开发者们~有没有想过,用一套代码就能让你的计算器应用在鸿蒙设备上完美运行呢?今天要和大家分享的,是一个超级实用又经典的功能------计算器!无论是日常算账、学习数学,还是工作中快速计算,这个小小的计算器都能成为你应用中最贴心的功能模块呢~

计算器虽然看起来简单,但实现一个功能完善、交互友好的计算器可不容易哦!它涉及状态管理、表达式解析、科学计算等多个技术要点。而 Flutter for OpenHarmony 让我们能够用熟悉的 Flutter 技术,轻松实现跨平台的计算器功能,是不是很心动呢?

本文将带领大家使用 Flutter for OpenHarmony 跨平台技术,从零开始实现一个功能完善的计算器。支持标准模式和科学模式,还有历史记录功能哦~让我们一起开启这段美妙的开发之旅吧!


二、技术背景

2.1 Flutter for OpenHarmony 简介

Flutter 就像是一个神奇的魔法棒,轻轻一挥,你的应用就能在鸿蒙、Android、iOS 上自由奔跑啦!Flutter 是 Google 推出的开源 UI 框架,以其"一次编写,多处运行"的理念深受开发者喜爱。而 Flutter for OpenHarmony 则是为我们打开了一扇通往鸿蒙生态的大门,让 Flutter 开发者能够无缝地将应用部署到鸿蒙设备上~

开源鸿蒙(OpenHarmony)是由开放原子开源基金会孵化的开源项目,致力于构建万物智联的操作系统生态。Flutter for OpenHarmony 的出现,极大地降低了跨平台开发的门槛,让更多开发者能够参与到鸿蒙生态的建设中来!

2.2 计算器的技术要点

实现一个完善的计算器,需要掌握以下技术要点:

状态管理:管理显示屏内容、当前操作符、操作数等状态,确保计算逻辑正确。

表达式解析:处理用户输入的数字和操作符,构建计算表达式。

科学计算:实现三角函数、对数、幂运算等科学计算功能。

UI布局:使用 GridView 构建按钮网格,实现美观的界面布局。

2.3 Flutter 与原生鸿蒙开发的对比

特性 Flutter for OpenHarmony 原生鸿蒙开发
学习曲线 较平缓,Dart语言简洁 ArkTS/ArkUI需要学习
跨平台能力 一套代码多端运行 仅限鸿蒙平台
数学运算 dart:math 库功能丰富 需要手动实现
UI布局 GridView便捷 需要手动布局
开发效率 热重载,开发快 需要重新编译

三、功能设计

3.1 功能概述

我们要实现的计算器,功能可丰富啦!包括:

标准模式:支持加减乘除、百分比、正负号切换等基本运算~

科学模式:支持三角函数、对数、幂运算、阶乘等高级运算!

历史记录:记录最近10条计算历史,方便查看~

表达式显示:实时显示当前计算表达式,清晰明了!

退格功能:输错了?轻轻一点就能删除~

3.2 界面设计

界面分为两种模式:

标准模式

  • 显示区域:表达式 + 计算结果
  • 历史记录:横向滚动显示
  • 模式切换:标准/科学切换按钮
  • 按钮区域:4×5 网格布局

科学模式

  • 科学按钮区:5×3 网格布局
  • 标准按钮区:4×5 网格布局
  • 支持更多数学函数

四、核心实现

4.1 状态变量设计

首先,我们需要设计状态变量来管理计算器的状态:

dart 复制代码
// 显示屏内容
String _display = '0';

// 当前表达式
String _expression = '';

// 第一个操作数
double? _firstOperand;

// 当前操作符
String? _operator;

// 是否等待第二个操作数
bool _waitingForSecondOperand = false;

// 是否科学模式
bool _isScientific = false;

// 计算历史
final List<String> _history = [];

4.2 数字输入处理

当用户按下数字按钮时:

dart 复制代码
void _onNumberPressed(String number) {
  setState(() {
    if (_waitingForSecondOperand) {
      _display = number;
      _waitingForSecondOperand = false;
    } else {
      _display = _display == '0' ? number : _display + number;
    }
  });
}

4.3 运算符处理

当用户按下运算符时:

dart 复制代码
void _onOperatorPressed(String op) {
  setState(() {
    if (_operator != null && !_waitingForSecondOperand) {
      _calculate();
    }
    _firstOperand = double.parse(_display);
    _operator = op;
    _expression = '$_display $op';
    _waitingForSecondOperand = true;
  });
}

4.4 计算逻辑实现

核心计算逻辑:

dart 复制代码
void _calculate() {
  if (_firstOperand == null || _operator == null) return;
  double secondOperand = double.parse(_display);
  double result = 0;
  
  switch (_operator!) {
    case '+': result = _firstOperand! + secondOperand; break;
    case '-': result = _firstOperand! - secondOperand; break;
    case '×': result = _firstOperand! * secondOperand; break;
    case '÷': result = secondOperand != 0 
        ? _firstOperand! / secondOperand 
        : double.infinity; 
      break;
    case '%': result = _firstOperand! % secondOperand; break;
    case '^': result = math.pow(_firstOperand!, secondOperand).toDouble(); break;
  }
  
  // 添加到历史记录
  String historyItem = '$_firstOperand $_operator $secondOperand = $result';
  
  setState(() {
    _display = _formatResult(result);
    _expression = '';
    _operator = null;
    _firstOperand = null;
    _history.insert(0, historyItem);
    if (_history.length > 10) _history.removeLast();
  });
}

4.5 科学计算函数

科学计算功能的实现:

dart 复制代码
void _onScientificOp(String op) {
  double value = double.parse(_display);
  double result = 0;
  
  switch (op) {
    case 'sin': result = math.sin(value * math.pi / 180); break;
    case 'cos': result = math.cos(value * math.pi / 180); break;
    case 'tan': result = math.tan(value * math.pi / 180); break;
    case 'log': result = math.log(value); break;
    case 'ln': result = math.log(value); break;
    case '√': result = math.sqrt(value); break;
    case 'x²': result = value * value; break;
    case '1/x': result = 1 / value; break;
    case '!': result = _factorial(value.toInt()).toDouble(); break;
    case 'π': result = math.pi; break;
    case 'e': result = math.e; break;
  }
  
  setState(() {
    _display = _formatResult(result);
  });
}

五、完整代码实现

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

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

  @override
  State<CalculatorFeature> createState() => _CalculatorFeatureState();
}

class _CalculatorFeatureState extends State<CalculatorFeature> {
  String _display = '0';
  String _expression = '';
  double? _firstOperand;
  String? _operator;
  bool _waitingForSecondOperand = false;
  bool _isScientific = false;
  final List<String> _history = [];

  void _onNumberPressed(String number) {
    setState(() {
      if (_waitingForSecondOperand) {
        _display = number;
        _waitingForSecondOperand = false;
      } else {
        _display = _display == '0' ? number : _display + number;
      }
    });
  }

  void _onOperatorPressed(String op) {
    setState(() {
      if (_operator != null && !_waitingForSecondOperand) {
        _calculate();
      }
      _firstOperand = double.parse(_display);
      _operator = op;
      _expression = '$_display $op';
      _waitingForSecondOperand = true;
    });
  }

  void _calculate() {
    if (_firstOperand == null || _operator == null) return;
    double secondOperand = double.parse(_display);
    double result = 0;
    String op = _operator!;

    switch (op) {
      case '+':
        result = _firstOperand! + secondOperand;
        break;
      case '-':
        result = _firstOperand! - secondOperand;
        break;
      case '×':
        result = _firstOperand! * secondOperand;
        break;
      case '÷':
        result = secondOperand != 0 ? _firstOperand! / secondOperand : double.infinity;
        break;
      case '%':
        result = _firstOperand! % secondOperand;
        break;
      case '^':
        result = math.pow(_firstOperand!, secondOperand).toDouble();
        break;
    }

    String historyItem = '$_firstOperand $op $secondOperand = $result';

    setState(() {
      _display = _formatResult(result);
      _expression = '';
      _operator = null;
      _firstOperand = null;
      _history.insert(0, historyItem);
      if (_history.length > 10) _history.removeLast();
    });
  }

  String _formatResult(double result) {
    if (result == result.toInt()) {
      return result.toInt().toString();
    }
    return result.toStringAsFixed(8).replaceAll(RegExp(r'0+$'), '').replaceAll(RegExp(r'\.$'), '');
  }

  void _onScientificOp(String op) {
    double value = double.parse(_display);
    double result = 0;

    switch (op) {
      case 'sin':
        result = math.sin(value * math.pi / 180);
        break;
      case 'cos':
        result = math.cos(value * math.pi / 180);
        break;
      case 'tan':
        result = math.tan(value * math.pi / 180);
        break;
      case 'log':
        result = math.log(value);
        break;
      case 'ln':
        result = math.log(value);
        break;
      case '√':
        result = math.sqrt(value);
        break;
      case 'x²':
        result = value * value;
        break;
      case '1/x':
        result = 1 / value;
        break;
      case '!':
        result = _factorial(value.toInt()).toDouble();
        break;
      case 'π':
        result = math.pi;
        break;
      case 'e':
        result = math.e;
        break;
    }

    setState(() {
      _display = _formatResult(result);
    });
  }

  int _factorial(int n) {
    if (n <= 1) return 1;
    return n * _factorial(n - 1);
  }

  void _onClear() {
    setState(() {
      _display = '0';
      _expression = '';
      _firstOperand = null;
      _operator = null;
      _waitingForSecondOperand = false;
    });
  }

  void _onBackspace() {
    setState(() {
      if (_display.length > 1) {
        _display = _display.substring(0, _display.length - 1);
      } else {
        _display = '0';
      }
    });
  }

  void _onDecimal() {
    if (_waitingForSecondOperand) {
      setState(() {
        _display = '0.';
        _waitingForSecondOperand = false;
      });
    } else if (!_display.contains('.')) {
      setState(() => _display += '.');
    }
  }

  void _onPlusMinus() {
    setState(() {
      if (_display != '0') {
        if (_display.startsWith('-')) {
          _display = _display.substring(1);
        } else {
          _display = '-$_display';
        }
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        _buildDisplay(),
        if (_history.isNotEmpty) _buildHistory(),
        const Divider(height: 1),
        _buildModeSwitch(),
        Expanded(
          child: _isScientific ? _buildScientificPad() : _buildBasicPad(),
        ),
      ],
    );
  }

  Widget _buildDisplay() {
    return Container(
      padding: const EdgeInsets.all(20),
      alignment: Alignment.centerRight,
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.end,
        children: [
          Text(
            _expression,
            style: const TextStyle(fontSize: 20, color: Colors.grey),
          ),
          const SizedBox(height: 8),
          SingleChildScrollView(
            scrollDirection: Axis.horizontal,
            child: Text(
              _display,
              style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold),
            ),
          ),
        ],
      ),
    );
  }

  Widget _buildHistory() {
    return Container(
      height: 60,
      padding: const EdgeInsets.symmetric(horizontal: 12),
      child: ListView.builder(
        scrollDirection: Axis.horizontal,
        itemCount: _history.length,
        itemBuilder: (context, index) {
          return Container(
            margin: const EdgeInsets.only(right: 8),
            padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
            decoration: BoxDecoration(
              color: Colors.grey.shade100,
              borderRadius: BorderRadius.circular(8),
            ),
            child: Center(
              child: Text(_history[index], style: const TextStyle(fontSize: 12)),
            ),
          );
        },
      ),
    );
  }

  Widget _buildModeSwitch() {
    return Padding(
      padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
      child: Row(
        children: [
          ChoiceChip(
            label: const Text('标准'),
            selected: !_isScientific,
            onSelected: (selected) => setState(() => _isScientific = false),
          ),
          const SizedBox(width: 8),
          ChoiceChip(
            label: const Text('科学'),
            selected: _isScientific,
            onSelected: (selected) => setState(() => _isScientific = true),
          ),
        ],
      ),
    );
  }

  Widget _buildBasicPad() {
    return GridView.count(
      crossAxisCount: 4,
      children: [
        _buildButton('C', Colors.grey.shade400, _onClear),
        _buildButton('⌫', Colors.grey.shade400, _onBackspace),
        _buildButton('%', Colors.grey.shade400, () => _onOperatorPressed('%')),
        _buildButton('÷', Colors.orange, () => _onOperatorPressed('÷')),
        _buildButton('7', Colors.white, () => _onNumberPressed('7')),
        _buildButton('8', Colors.white, () => _onNumberPressed('8')),
        _buildButton('9', Colors.white, () => _onNumberPressed('9')),
        _buildButton('×', Colors.orange, () => _onOperatorPressed('×')),
        _buildButton('4', Colors.white, () => _onNumberPressed('4')),
        _buildButton('5', Colors.white, () => _onNumberPressed('5')),
        _buildButton('6', Colors.white, () => _onNumberPressed('6')),
        _buildButton('-', Colors.orange, () => _onOperatorPressed('-')),
        _buildButton('1', Colors.white, () => _onNumberPressed('1')),
        _buildButton('2', Colors.white, () => _onNumberPressed('2')),
        _buildButton('3', Colors.white, () => _onNumberPressed('3')),
        _buildButton('+', Colors.orange, () => _onOperatorPressed('+')),
        _buildButton('±', Colors.white, _onPlusMinus),
        _buildButton('0', Colors.white, () => _onNumberPressed('0')),
        _buildButton('.', Colors.white, _onDecimal),
        _buildButton('=', Colors.orange, _calculate),
      ],
    );
  }

  Widget _buildScientificPad() {
    return Column(
      children: [
        Expanded(
          child: GridView.count(
            crossAxisCount: 5,
            children: [
              _buildSciButton('sin'),
              _buildSciButton('cos'),
              _buildSciButton('tan'),
              _buildSciButton('log'),
              _buildSciButton('ln'),
              _buildSciButton('√'),
              _buildSciButton('x²'),
              _buildSciButton('^'),
              _buildSciButton('1/x'),
              _buildSciButton('!'),
              _buildSciButton('π'),
              _buildSciButton('e'),
              _buildSciButton('('),
              _buildSciButton(')'),
              _buildSciButton('C', isControl: true),
            ],
          ),
        ),
        Container(
          height: 200,
          child: _buildBasicPad(),
        ),
      ],
    );
  }

  Widget _buildSciButton(String text, {bool isControl = false}) {
    return Container(
      margin: const EdgeInsets.all(2),
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(
          backgroundColor: isControl ? Colors.grey.shade400 : Colors.blue.shade100,
          foregroundColor: isControl ? Colors.black : Colors.blue,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
        ),
        onPressed: () => _onScientificOp(text),
        child: Text(text, style: const TextStyle(fontSize: 14)),
      ),
    );
  }

  Widget _buildButton(String text, Color color, VoidCallback onPressed) {
    return Container(
      margin: const EdgeInsets.all(4),
      child: ElevatedButton(
        style: ElevatedButton.styleFrom(
          backgroundColor: color,
          foregroundColor: color == Colors.white ? Colors.black : Colors.white,
          shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
        ),
        onPressed: onPressed,
        child: Text(text, style: const TextStyle(fontSize: 24)),
      ),
    );
  }
}

六、运行效果


七、关键技术点解析

7.1 GridView 构建按钮网格

Flutter 的 GridView 组件非常适合构建计算器按钮网格:

dart 复制代码
GridView.count(
  crossAxisCount: 4,  // 4列布局
  children: [
    _buildButton('C', Colors.grey.shade400, _onClear),
    _buildButton('⌫', Colors.grey.shade400, _onBackspace),
    // ... 更多按钮
  ],
)

使用 crossAxisCount 设置列数,GridView 会自动计算行数和布局,超级方便~

7.2 dart:math 科学计算

Flutter 内置的 dart:math 库提供了丰富的数学函数:

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

// 三角函数(注意角度转弧度)
math.sin(value * math.pi / 180)

// 平方根
math.sqrt(value)

// 幂运算
math.pow(base, exponent)

// 常量
math.pi  // 圆周率
math.e   // 自然对数底

7.3 ChoiceChip 模式切换

ChoiceChip 是 Material Design 的选择芯片组件,非常适合做模式切换:

dart 复制代码
ChoiceChip(
  label: const Text('标准'),
  selected: !_isScientific,
  onSelected: (selected) => setState(() => _isScientific = false),
)

7.4 OpenHarmony 平台适配要点

在 OpenHarmony 设备上运行 Flutter 应用,需要注意:

  1. 签名配置:需在 DevEco Studio 中配置应用签名
  2. 触摸响应:使用 ElevatedButton 处理触摸事件
  3. 数学运算:dart:math 库在鸿蒙平台完全兼容

八、总结与展望

通过本文的学习,我们使用 Flutter for OpenHarmony 成功实现了一个功能完善的计算器!从基本四则运算到科学计算,从状态管理到 UI 布局,每一个功能都体现了 Flutter 跨平台开发的便捷与高效~

功能回顾

  • ✅ 标准模式:加减乘除、百分比、正负号
  • ✅ 科学模式:三角函数、对数、幂运算、阶乘
  • ✅ 历史记录:保存最近10条计算记录
  • ✅ 表达式显示:实时显示当前计算表达式
  • ✅ 退格功能:支持删除输入

可扩展方向

  • 表达式解析:支持复杂表达式如 (1+2)*3
  • 主题切换:支持明暗主题切换
  • 语音播报:计算结果语音播报
  • 单位换算:集成单位换算功能

Flutter for OpenHarmony 的生态正在蓬勃发展,越来越多的开发者加入到这个大家庭中。相信在不久的将来,我们会看到更多优秀的跨平台应用诞生!让我们一起为鸿蒙生态贡献自己的力量吧~

相关推荐
jiejiejiejie_3 小时前
Flutter for OpenHarmony 交互体验实战合集:底部导航优化 + 萌系用户反馈全攻略
flutter
liulian09164 小时前
Flutter for OpenHarmony 跨平台开发:番茄钟功能实战指南
flutter
liulian09165 小时前
Flutter for OpenHarmony 效率工具开发实战:我实现的番茄钟与倒计时功能总结
flutter
jiejiejiejie_6 小时前
Flutter for OpenHarmony 跨平台开发:待办事项功能实战指南
flutter
maaath7 小时前
【maaath】Flutter for OpenHarmony 实战:电影榜单应用开发指南
flutter·华为·harmonyos
xmdy586610 小时前
Flutter+开源鸿蒙实战|智安盾电商溯源平台Day6 登录逻辑+积分体系+全局收尾优化
flutter·华为·harmonyos
liulian091610 小时前
Flutter for OpenHarmony 工具类应用实战总结:计算器 + 记事本功能开发全解析
flutter
911hzh12 小时前
Flutter WebRTC iOS 原理解析:从 getUserMedia 到 Texture,讲清视频采集、纹理渲染与远端通话链路
flutter·ios·webrtc
xmdy586612 小时前
Flutter+开源鸿蒙实战|智联邻里Day1 项目搭建+环境适配+架构规划(十五五民生创新版)
flutter·开源·harmonyos