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 应用,需要注意:
- 签名配置:需在 DevEco Studio 中配置应用签名
- 触摸响应:使用 ElevatedButton 处理触摸事件
- 数学运算:dart:math 库在鸿蒙平台完全兼容
八、总结与展望
通过本文的学习,我们使用 Flutter for OpenHarmony 成功实现了一个功能完善的计算器!从基本四则运算到科学计算,从状态管理到 UI 布局,每一个功能都体现了 Flutter 跨平台开发的便捷与高效~
功能回顾:
- ✅ 标准模式:加减乘除、百分比、正负号
- ✅ 科学模式:三角函数、对数、幂运算、阶乘
- ✅ 历史记录:保存最近10条计算记录
- ✅ 表达式显示:实时显示当前计算表达式
- ✅ 退格功能:支持删除输入
可扩展方向:
- 表达式解析:支持复杂表达式如
(1+2)*3 - 主题切换:支持明暗主题切换
- 语音播报:计算结果语音播报
- 单位换算:集成单位换算功能
Flutter for OpenHarmony 的生态正在蓬勃发展,越来越多的开发者加入到这个大家庭中。相信在不久的将来,我们会看到更多优秀的跨平台应用诞生!让我们一起为鸿蒙生态贡献自己的力量吧~