前言
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
calculator 是一个基于 Flutter 的轻量级 Calculator 计算器 项目,源码集中在 lib/main.dart,同时保留了 ohos 平台工程目录。效果图如下:

本文重点回答三个问题:
calculator的计算流程是怎样组织的。- Flutter 页面状态如何驱动计算器 UI 刷新。
- 这个项目迁移或适配 OpenHarmony 时应重点检查哪些位置。
一、项目背景与功能定位
1.1 项目功能概览
calculator 的功能边界很清晰:用户点击数字按钮输入数值,点击 +、-、×、÷ 保存运算符,再点击 = 得到结果。
项目还提供了 C 清空按钮、小数点输入按钮,以及除法时对 num2 == 0 的错误保护。
它没有引入复杂第三方依赖,因此特别适合讲解 Flutter 小工具从页面到逻辑的一体化实现。
1.2 技术关键词
本项目可以提炼出以下技术关键词:
- Flutter MaterialApp:负责应用级配置、主题色和首页挂载。
- StatefulWidget:承载需要变化的计算器状态。
- setState:在按钮点击后触发 UI 重建。
- Expanded + Row + Column:构建计算器常见的网格按钮布局。
- OpenHarmony ohos 工程:承载平台侧配置和入口能力。
1.3 适合读者
本文适合以下读者阅读:
- 刚开始学习 Flutter 页面状态管理的开发者。
- 希望把 Flutter 小应用整理成 OpenHarmony 适配文章的作者。
- 需要一篇 CSDN 高分结构模板并希望内容贴合真实项目的技术写作者。
提示:计算器项目虽然功能简单,但它包含输入、状态、分支、错误处理和布局复用,特别适合作为 Flutter 入门到平台适配之间的桥梁案例。
二、项目目录结构分析
2.1 根目录文件
当前项目根目录包含 Flutter 标准工程文件,也包含 OpenHarmony 平台目录。
| 文件/目录 | 当前项目中的作用 | 文章讲解重点 |
|---|---|---|
lib/main.dart |
计算器入口、状态和 UI 全部集中在这里 | 重点拆解计算逻辑和按钮布局 |
pubspec.yaml |
定义 Flutter SDK、版本和依赖 | 说明依赖简单、适合跨平台适配 |
analysis_options.yaml |
Dart 静态分析配置 | 保持代码风格一致 |
test/ |
Flutter 测试目录 | 可扩展计算逻辑单元测试 |
ohos/ |
OpenHarmony 平台工程 | 适配、签名、入口和资源配置 |
2.2 OpenHarmony 目录
ohos 目录下可以看到 AppScope、entry、EntryAbility.ets、Index.ets、GeneratedPluginRegistrant.ets 等文件。
这说明项目不仅是普通 Flutter Demo,也已经具备 OpenHarmony 工程外壳。
| OpenHarmony 文件 | 作用 | 检查建议 |
|---|---|---|
AppScope/app.json5 |
应用级元信息 | 检查应用名、图标和包信息 |
entry/src/main/module.json5 |
模块配置 | 检查入口、权限和设备类型 |
EntryAbility.ets |
Ability 生命周期入口 | 检查启动流程是否正常 |
Index.ets |
页面承载入口 | 检查 Flutter 页面嵌入表现 |
GeneratedPluginRegistrant.ets |
插件注册 | 当前依赖简单,重点确认无多余插件异常 |
2.3 代码集中度
这个项目的主要业务代码都在一个 main.dart 文件里。
这种组织方式对小 Demo 很友好,读者打开一个文件就能看到完整链路。
如果项目后续变复杂,可以再拆分为:
calculator_state.dart:保存计算状态。calculator_keyboard.dart:封装按钮网格。calculator_display.dart:封装结果显示区。calculator_logic.dart:抽离四则运算逻辑。
三、环境准备与依赖配置
3.1 pubspec 基础配置
项目的 pubspec.yaml 中名称为 calculator,版本为 1.0.0+1,Dart SDK 约束为 ^3.9.2。
yaml
name: calculator
description: "A new Flutter project."
publish_to: 'none'
version: 1.0.0+1
environment:
sdk: ^3.9.2
这段配置说明项目是一个私有示例应用,不会误发布到 pub.dev。
3.2 Flutter 依赖
项目只依赖 Flutter SDK 和 cupertino_icons。
yaml
dependencies:
flutter:
sdk: flutter
cupertino_icons: ^1.0.8
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^5.0.0
依赖越简单,OpenHarmony 适配时排查成本越低。
3.3 推荐工具链
| 工具 | 作用 | 推荐理由 |
|---|---|---|
| Flutter SDK | 编译运行 Flutter 页面 | 项目核心运行环境 |
| Dart SDK | 语言分析、格式化、测试 | 随 Flutter 自带 |
| DevEco Studio | 查看 OpenHarmony 工程 | 检查 ohos 平台配置 |
| CSDN Markdown 编辑器 | 发布文章 | 校验代码块、表格和图片展示 |
四、应用入口源码解读
4.1 main 函数
lib/main.dart 的入口非常短,直接启动 CalculatorApp。
dart
import 'package:flutter/material.dart';
void main() {
runApp(const CalculatorApp());
}
这段代码体现了 Flutter 应用的标准启动方式。
runApp 会把 CalculatorApp 挂载到渲染树根部,后续所有页面和状态都从这个入口向下展开。
4.2 CalculatorApp 结构
CalculatorApp 是一个 StatelessWidget,因为它本身不直接保存计算状态。
dart
class CalculatorApp extends StatelessWidget {
const CalculatorApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Calculator',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const CalculatorHomePage(title: 'Calculator'),
);
}
}
这里有三个值得写进文章的点:
title: 'Calculator'说明应用主题明确。ColorScheme.fromSeed(seedColor: Colors.blue)让主题色统一。home: CalculatorHomePage把计算器首页挂载为默认页面。
4.3 Material 3 配置
项目启用了 useMaterial3: true。
这意味着按钮、AppBar、默认主题会遵循 Material 3 风格。
对于 OpenHarmony 适配文章而言,这一点可以用来说明 Flutter 自身 UI 体系如何在不同平台保持一致。
五、页面类与状态字段设计
5.1 StatefulWidget 分工
计算器页面由 CalculatorHomePage 和 _CalculatorHomePageState 共同完成。
dart
class CalculatorHomePage extends StatefulWidget {
const CalculatorHomePage({super.key, required this.title});
final String title;
@override
State<CalculatorHomePage> createState() => _CalculatorHomePageState();
}
CalculatorHomePage 只保存不可变的标题,真正变化的数据放到 State 类里。
5.2 状态字段
项目中定义了五个核心状态字段。
dart
class _CalculatorHomePageState extends State<CalculatorHomePage> {
String output = "0";
double num1 = 0;
double num2 = 0;
String operand = "";
bool shouldResetScreen = false;
}
字段含义如下:
| 字段 | 类型 | 真实作用 |
|---|---|---|
output |
String |
当前屏幕显示内容,默认是 "0" |
num1 |
double |
点击运算符前保存的第一个数字 |
num2 |
double |
点击等号后解析出的第二个数字 |
operand |
String |
保存 +、-、×、÷ |
shouldResetScreen |
bool |
控制输入第二个数字时是否清空屏幕 |
5.3 状态设计优点
这个状态模型很适合入门案例。
它没有额外类,也没有复杂状态管理框架,却能表达一个计算器的完整生命周期。
核心流程可以概括为:
- 初始时
output = "0"。 - 用户输入数字后更新
output。 - 用户点击运算符后保存
num1和operand。 - 用户输入第二个数字。
- 点击
=后计算结果并写回output。
注意:
shouldResetScreen是这个项目里非常关键的状态。如果没有它,点击运算符后继续输入数字时,第二个数字会错误地拼接到第一个数字后面。
六、核心计算逻辑拆解
6.1 buttonPressed 方法入口
所有按钮最终都会调用 buttonPressed。
dart
void buttonPressed(String buttonText) {
setState(() {
if (buttonText == "C") {
output = "0";
num1 = 0;
num2 = 0;
operand = "";
shouldResetScreen = false;
}
});
}
真实源码中这个方法包含多个分支,文章可以按按钮类型分段讲解。
6.2 清空逻辑
当按钮文本是 C 时,项目会重置所有状态。
dart
if (buttonText == "C") {
output = "0";
num1 = 0;
num2 = 0;
operand = "";
shouldResetScreen = false;
}
这个分支非常直观,适合解释"状态归零"的意义。
6.3 运算符逻辑
点击 +、-、×、÷ 时,项目会保存当前屏幕值。
dart
} else if (buttonText == "+" || buttonText == "-" ||
buttonText == "×" || buttonText == "÷") {
num1 = double.parse(output);
operand = buttonText;
shouldResetScreen = true;
}
这里的关键是 shouldResetScreen = true。
下一次点击数字时,页面不会继续拼接旧值,而是开启第二个操作数输入。
6.4 等号计算逻辑
点击 = 后,源码会根据 operand 执行对应运算。
dart
} else if (buttonText == "=") {
num2 = double.parse(output);
double result = 0;
if (operand == "+") {
result = num1 + num2;
} else if (operand == "-") {
result = num1 - num2;
} else if (operand == "×") {
result = num1 * num2;
} else if (operand == "÷") {
if (num2 != 0) {
result = num1 / num2;
} else {
output = "Error";
return;
}
}
}
这段代码已经覆盖四则运算,还对除零做了保护。
6.5 结果格式化
项目没有直接展示 double.toString(),而是做了结果格式化。
dart
output = result.toString().endsWith('.0')
? result.toInt().toString()
: result.toStringAsFixed(8)
.replaceAll(RegExp(r'0+$'), '')
.replaceAll(RegExp(r'\.$'), '');
这个细节值得单独写,因为它提升了用户体验。
例如 1 + 1 会显示 2,而不是 2.0。
6.6 数字与小数点输入
数字输入和小数点输入分支如下。
dart
} else if (buttonText == ".") {
if (!output.contains(".")) {
output = output + buttonText;
}
} else {
if (output == "0" || shouldResetScreen) {
output = buttonText;
shouldResetScreen = false;
} else {
output = output + buttonText;
}
}
这里有两个实用点:
- 小数点只能输入一次。
- 运算符之后第一次输入数字会重置屏幕。
七、按钮组件封装
7.1 buildButton 方法
项目通过 buildButton 复用按钮样式。
dart
Widget buildButton(String buttonText, Color color, {int flex = 1}) {
return Expanded(
flex: flex,
child: Padding(
padding: const EdgeInsets.all(4.0),
child: ElevatedButton(
onPressed: () => buttonPressed(buttonText),
style: ElevatedButton.styleFrom(
backgroundColor: color,
padding: const EdgeInsets.all(24),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: Text(
buttonText,
style: const TextStyle(fontSize: 28, fontWeight: FontWeight.bold, color: Colors.white),
),
),
),
);
}
这是整篇文章里最能体现组件复用的一段代码。
7.2 参数设计
buildButton 有三个参数。
| 参数 | 类型 | 用途 |
|---|---|---|
buttonText |
String |
显示按钮文本并传给 buttonPressed |
color |
Color |
控制按钮背景色 |
flex |
int |
控制按钮宽度比例,默认是 1 |
7.3 样式选择
按钮使用 ElevatedButton,并设置了:
backgroundColor:区分数字、运算符、清空和等号。padding: EdgeInsets.all(24):保证点击区域足够大。BorderRadius.circular(12):让按钮有圆角。TextStyle(fontSize: 28, fontWeight: FontWeight.bold):增强可读性。
八、页面布局实现
8.1 Scaffold 与 AppBar
页面根结构使用 Scaffold。
dart
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: Column(
children: [
// display area
// keyboard area
],
),
);
AppBar 的标题来自 widget.title,也就是 CalculatorHomePage(title: 'Calculator') 传入的值。
8.2 显示区域
计算器顶部显示当前结果。
dart
Expanded(
child: Container(
alignment: Alignment.centerRight,
padding: const EdgeInsets.all(24),
child: Text(
output,
style: const TextStyle(fontSize: 64, fontWeight: FontWeight.bold),
),
),
)
这个区域把文字右对齐,符合计算器常见交互习惯。
8.3 按钮区域
按钮区使用 Expanded(flex: 3) 占据更大空间。
dart
Expanded(
flex: 3,
child: Column(
children: [
Row(children: [
buildButton("C", Colors.red),
buildButton("÷", Colors.orange),
buildButton("×", Colors.orange),
buildButton("⌫", Colors.grey),
]),
],
),
)
按钮行按照计算器键盘布局组织,清空、运算符和数字区域区分明显。
8.4 当前源码中的可改进点
项目中展示了 ⌫ 退格按钮,但 buttonPressed 目前没有处理 ⌫ 分支。
这是一处很适合在文章中指出的真实优化点。
可以补充如下逻辑:
dart
} else if (buttonText == "⌫") {
if (output.length > 1 && output != "Error") {
output = output.substring(0, output.length - 1);
} else {
output = "0";
}
}
这个优化来自当前项目真实 UI,不是凭空添加功能。
九、OpenHarmony 适配说明
9.1 为什么这个项目适合适配
calculator 不依赖平台传感器、相机、网络或数据库,因此 OpenHarmony 适配重点主要集中在工程配置和 UI 运行效果。
这类项目可以作为 Flutter/OpenHarmony 环境是否打通的基础验证项目。
9.2 ohos 工程检查点
适配时建议重点检查以下内容:
ohos/AppScope/app.json5中应用信息是否正确。ohos/entry/src/main/module.json5中入口模块是否正常。EntryAbility.ets是否能够启动页面。- 资源目录中的图标和字符串是否能正确加载。
- 运行时按钮点击、字体大小和布局比例是否正常。
9.3 构建命令
可以先从 Flutter 标准命令开始检查。
bash
flutter pub get
flutter analyze
flutter test
flutter run
如果本地已经配置 OpenHarmony Flutter 工具链,再补充对应的 OpenHarmony 构建命令。
bash
flutter clean
flutter pub get
flutter run -d <openharmony-device-id>
9.4 平台验证表
| 验证项 | Flutter 侧表现 | OpenHarmony 侧检查 |
|---|---|---|
| 应用启动 | CalculatorApp 正常挂载 |
Ability 能启动页面 |
| 结果显示 | output 正常刷新 |
字体大小不溢出 |
| 按钮点击 | buttonPressed 被触发 |
点击区域响应正常 |
| 除零保护 | 显示 Error |
错误状态不闪退 |
| 主题色 | 蓝色 Material 主题 | AppBar 和按钮颜色一致 |
十、测试策略与质量保障
10.1 当前测试方向
项目已有 test 目录,后续可以把计算逻辑抽成纯函数后测试。
例如可以定义一个 calculate 方法。
dart
double calculate(double left, double right, String op) {
switch (op) {
case '+':
return left + right;
case '-':
return left - right;
case '×':
return left * right;
case '÷':
if (right == 0) {
throw ArgumentError('Cannot divide by zero');
}
return left / right;
default:
throw ArgumentError('Unknown operator');
}
}
然后编写单元测试。
dart
import 'package:flutter_test/flutter_test.dart';
void main() {
test('calculator addition should work', () {
expect(1 + 2, 3);
});
test('calculator division by zero should be guarded', () {
expect(() => 1 ~/ 0, throwsA(isA<IntegerDivisionByZeroException>()));
});
}
10.2 手工测试清单
发布前建议手工验证以下场景:
- 连续输入多个数字,例如
123。 - 输入小数,例如
1.25。 - 执行
+、-、×、÷四种运算。 - 执行
1 ÷ 0,确认显示Error。 - 点击
C后所有状态恢复初始值。 - 点击 UI 中的
⌫,确认是否按预期处理。
10.3 静态分析
Flutter 项目建议始终运行静态分析。
bash
flutter analyze
dart format lib test
flutter test
如果 ⌫ 按钮没有对应逻辑,不一定会导致静态分析失败,但会影响真实功能完整性。
十二、常见问题与优化建议
12.1 除零错误如何处理
当前项目已经处理除零问题。
dart
if (num2 != 0) {
result = num1 / num2;
} else {
output = "Error";
return;
}
这个处理可以避免直接显示无意义结果。
12.2 退格按钮如何补齐
UI 中有 ⌫ 按钮,但当前逻辑没有对应分支。
这是本文最重要的源码级优化建议。
建议在 C 分支之后增加退格处理,让 UI 和逻辑保持一致。
12.3 连续运算如何增强
当前项目更接近"两个数字一次运算"的模式。
如果要支持 1 + 2 + 3 = 6 这种连续运算,可以在点击第二个运算符时立即计算上一次结果。
12.4 输入错误如何增强
当前 double.parse(output) 假设 output 一定可解析。
如果 output == "Error" 后继续点击运算符,建议先重置或忽略。
12.5 UI 响应式如何增强
按钮使用较大的 padding 和字体。
在小屏设备上可能需要通过 LayoutBuilder 或动态字号优化。
如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!
相关资源: