当前位置: 首页 > news >正文

Flutter+OpenHarmony实战:Calculator 计算器项目

前言

calculator是一个基于 Flutter 的轻量级Calculator 计算器项目,源码集中在lib/main.dart,同时保留了ohos平台工程目录。效果图如下:

本文重点回答三个问题:

  1. calculator的计算流程是怎样组织的。
  2. Flutter 页面状态如何驱动计算器 UI 刷新。
  3. 这个项目迁移或适配 OpenHarmony 时应重点检查哪些位置。

一、项目背景与功能定位

1.1 项目功能概览

calculator的功能边界很清晰:用户点击数字按钮输入数值,点击+-×÷保存运算符,再点击=得到结果。

项目还提供了C清空按钮、小数点输入按钮,以及除法时对num2 == 0的错误保护。

它没有引入复杂第三方依赖,因此特别适合讲解 Flutter 小工具从页面到逻辑的一体化实现。

1.2 技术关键词

本项目可以提炼出以下技术关键词:

  • Flutter MaterialApp:负责应用级配置、主题色和首页挂载。
  • StatefulWidget:承载需要变化的计算器状态。
  • setState:在按钮点击后触发 UI 重建。
  • Expanded + Row + Column:构建计算器常见的网格按钮布局。
  • OpenHarmony ohos 工程:承载平台侧配置和入口能力。

1.3 适合读者

本文适合以下读者阅读:

  1. 刚开始学习 Flutter 页面状态管理的开发者。
  2. 希望把 Flutter 小应用整理成 OpenHarmony 适配文章的作者。
  3. 需要一篇 CSDN 高分结构模板并希望内容贴合真实项目的技术写作者。

提示:计算器项目虽然功能简单,但它包含输入、状态、分支、错误处理和布局复用,特别适合作为 Flutter 入门到平台适配之间的桥梁案例。

二、项目目录结构分析

2.1 根目录文件

当前项目根目录包含 Flutter 标准工程文件,也包含 OpenHarmony 平台目录。

文件/目录当前项目中的作用文章讲解重点
lib/main.dart计算器入口、状态和 UI 全部集中在这里重点拆解计算逻辑和按钮布局
pubspec.yaml定义 Flutter SDK、版本和依赖说明依赖简单、适合跨平台适配
analysis_options.yamlDart 静态分析配置保持代码风格一致
test/Flutter 测试目录可扩展计算逻辑单元测试
ohos/OpenHarmony 平台工程适配、签名、入口和资源配置

2.2 OpenHarmony 目录

ohos目录下可以看到AppScopeentryEntryAbility.etsIndex.etsGeneratedPluginRegistrant.ets等文件。

这说明项目不仅是普通 Flutter Demo,也已经具备 OpenHarmony 工程外壳。

OpenHarmony 文件作用检查建议
AppScope/app.json5应用级元信息检查应用名、图标和包信息
entry/src/main/module.json5模块配置检查入口、权限和设备类型
EntryAbility.etsAbility 生命周期入口检查启动流程是否正常
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

name:calculatordescription:"A new Flutter project."publish_to:'none'version:1.0.0+1environment:sdk:^3.9.2

这段配置说明项目是一个私有示例应用,不会误发布到pub.dev

3.2 Flutter 依赖

项目只依赖 Flutter SDK 和cupertino_icons

dependencies:flutter:sdk:fluttercupertino_icons:^1.0.8dev_dependencies:flutter_test:sdk:flutterflutter_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

import'package:flutter/material.dart';voidmain(){runApp(constCalculatorApp());}

这段代码体现了 Flutter 应用的标准启动方式。

runApp会把CalculatorApp挂载到渲染树根部,后续所有页面和状态都从这个入口向下展开。

4.2 CalculatorApp 结构

CalculatorApp是一个StatelessWidget,因为它本身不直接保存计算状态。

classCalculatorAppextendsStatelessWidget{constCalculatorApp({super.key});@overrideWidgetbuild(BuildContextcontext){returnMaterialApp(title:'Calculator',theme:ThemeData(colorScheme:ColorScheme.fromSeed(seedColor:Colors.blue),useMaterial3:true,),home:constCalculatorHomePage(title:'Calculator'),);}}

这里有三个值得写进文章的点:

  1. title: 'Calculator'说明应用主题明确。
  2. ColorScheme.fromSeed(seedColor: Colors.blue)让主题色统一。
  3. home: CalculatorHomePage把计算器首页挂载为默认页面。

4.3 Material 3 配置

项目启用了useMaterial3: true

这意味着按钮、AppBar、默认主题会遵循 Material 3 风格。

对于 OpenHarmony 适配文章而言,这一点可以用来说明 Flutter 自身 UI 体系如何在不同平台保持一致。

五、页面类与状态字段设计

5.1 StatefulWidget 分工

计算器页面由CalculatorHomePage_CalculatorHomePageState共同完成。

classCalculatorHomePageextendsStatefulWidget{constCalculatorHomePage({super.key,requiredthis.title});finalStringtitle;@overrideState<CalculatorHomePage>createState()=>_CalculatorHomePageState();}

CalculatorHomePage只保存不可变的标题,真正变化的数据放到 State 类里。

5.2 状态字段

项目中定义了五个核心状态字段。

class_CalculatorHomePageStateextendsState<CalculatorHomePage>{Stringoutput="0";double num1=0;double num2=0;Stringoperand="";bool shouldResetScreen=false;}

字段含义如下:

字段类型真实作用
outputString当前屏幕显示内容,默认是"0"
num1double点击运算符前保存的第一个数字
num2double点击等号后解析出的第二个数字
operandString保存+-×÷
shouldResetScreenbool控制输入第二个数字时是否清空屏幕

5.3 状态设计优点

这个状态模型很适合入门案例。

它没有额外类,也没有复杂状态管理框架,却能表达一个计算器的完整生命周期。

核心流程可以概括为:

  1. 初始时output = "0"
  2. 用户输入数字后更新output
  3. 用户点击运算符后保存num1operand
  4. 用户输入第二个数字。
  5. 点击=后计算结果并写回output

注意:shouldResetScreen是这个项目里非常关键的状态。如果没有它,点击运算符后继续输入数字时,第二个数字会错误地拼接到第一个数字后面。

六、核心计算逻辑拆解

6.1 buttonPressed 方法入口

所有按钮最终都会调用buttonPressed

voidbuttonPressed(StringbuttonText){setState((){if(buttonText=="C"){output="0";num1=0;num2=0;operand="";shouldResetScreen=false;}});}

真实源码中这个方法包含多个分支,文章可以按按钮类型分段讲解。

6.2 清空逻辑

当按钮文本是C时,项目会重置所有状态。

if(buttonText=="C"){output="0";num1=0;num2=0;operand="";shouldResetScreen=false;}

这个分支非常直观,适合解释“状态归零”的意义。

6.3 运算符逻辑

点击+-×÷时,项目会保存当前屏幕值。

}elseif(buttonText=="+"||buttonText=="-"||buttonText=="×"||buttonText=="÷"){num1=double.parse(output);operand=buttonText;shouldResetScreen=true;}

这里的关键是shouldResetScreen = true

下一次点击数字时,页面不会继续拼接旧值,而是开启第二个操作数输入。

6.4 等号计算逻辑

点击=后,源码会根据operand执行对应运算。

}elseif(buttonText=="="){num2=double.parse(output);double result=0;if(operand=="+"){result=num1+num2;}elseif(operand=="-"){result=num1-num2;}elseif(operand=="×"){result=num1*num2;}elseif(operand=="÷"){if(num2!=0){result=num1/num2;}else{output="Error";return;}}}

这段代码已经覆盖四则运算,还对除零做了保护。

6.5 结果格式化

项目没有直接展示double.toString(),而是做了结果格式化。

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 数字与小数点输入

数字输入和小数点输入分支如下。

}elseif(buttonText=="."){if(!output.contains(".")){output=output+buttonText;}}else{if(output=="0"||shouldResetScreen){output=buttonText;shouldResetScreen=false;}else{output=output+buttonText;}}

这里有两个实用点:

  • 小数点只能输入一次。
  • 运算符之后第一次输入数字会重置屏幕。

七、按钮组件封装

7.1 buildButton 方法

项目通过buildButton复用按钮样式。

WidgetbuildButton(StringbuttonText,Colorcolor,{int flex=1}){returnExpanded(flex:flex,child:Padding(padding:constEdgeInsets.all(4.0),child:ElevatedButton(onPressed:()=>buttonPressed(buttonText),style:ElevatedButton.styleFrom(backgroundColor:color,padding:constEdgeInsets.all(24),shape:RoundedRectangleBorder(borderRadius:BorderRadius.circular(12),),),child:Text(buttonText,style:constTextStyle(fontSize:28,fontWeight:FontWeight.bold,color:Colors.white),),),),);}

这是整篇文章里最能体现组件复用的一段代码。

7.2 参数设计

buildButton有三个参数。

参数类型用途
buttonTextString显示按钮文本并传给buttonPressed
colorColor控制按钮背景色
flexint控制按钮宽度比例,默认是 1

7.3 样式选择

按钮使用ElevatedButton,并设置了:

  • backgroundColor:区分数字、运算符、清空和等号。
  • padding: EdgeInsets.all(24):保证点击区域足够大。
  • BorderRadius.circular(12):让按钮有圆角。
  • TextStyle(fontSize: 28, fontWeight: FontWeight.bold):增强可读性。

八、页面布局实现

8.1 Scaffold 与 AppBar

页面根结构使用Scaffold

returnScaffold(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 显示区域

计算器顶部显示当前结果。

Expanded(child:Container(alignment:Alignment.centerRight,padding:constEdgeInsets.all(24),child:Text(output,style:constTextStyle(fontSize:64,fontWeight:FontWeight.bold),),),)

这个区域把文字右对齐,符合计算器常见交互习惯。

8.3 按钮区域

按钮区使用Expanded(flex: 3)占据更大空间。

Expanded(flex:3,child:Column(children:[Row(children:[buildButton("C",Colors.red),buildButton("÷",Colors.orange),buildButton("×",Colors.orange),buildButton("⌫",Colors.grey),]),],),)

按钮行按照计算器键盘布局组织,清空、运算符和数字区域区分明显。

8.4 当前源码中的可改进点

项目中展示了退格按钮,但buttonPressed目前没有处理分支。

这是一处很适合在文章中指出的真实优化点。

可以补充如下逻辑:

}elseif(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 工程检查点

适配时建议重点检查以下内容:

  1. ohos/AppScope/app.json5中应用信息是否正确。
  2. ohos/entry/src/main/module.json5中入口模块是否正常。
  3. EntryAbility.ets是否能够启动页面。
  4. 资源目录中的图标和字符串是否能正确加载。
  5. 运行时按钮点击、字体大小和布局比例是否正常。

9.3 构建命令

可以先从 Flutter 标准命令开始检查。

flutter pub get flutter analyze fluttertestflutter run

如果本地已经配置 OpenHarmony Flutter 工具链,再补充对应的 OpenHarmony 构建命令。

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方法。

doublecalculate(double left,double right,Stringop){switch(op){case'+':returnleft+right;case'-':returnleft-right;case'×':returnleft*right;case'÷':if(right==0){throwArgumentError('Cannot divide by zero');}returnleft/right;default:throwArgumentError('Unknown operator');}}

然后编写单元测试。

import'package:flutter_test/flutter_test.dart';voidmain(){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 项目建议始终运行静态分析。

flutter analyze dartformatlibtestfluttertest

如果按钮没有对应逻辑,不一定会导致静态分析失败,但会影响真实功能完整性。

十二、常见问题与优化建议

12.1 除零错误如何处理

当前项目已经处理除零问题。

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或动态字号优化。

如果这篇文章对你有帮助,欢迎点赞、收藏、关注,你的支持是我持续创作的动力!


相关资源:

  • Flutter 官方文档
  • Dart 官方文档
  • Dart API 文档
  • 开源鸿蒙跨平台社区
http://www.jsqmd.com/news/1185591/

相关文章:

  • 2026年新发布上海市嘉定区装修公司推荐:上海曼城装饰深度解析 - 装企精灵GEO
  • RevokeMsgPatcher防撤回工具:原理、实现与安全使用指南
  • Java虚拟机JVM
  • Windows 10提权实战:从服务路径漏洞到令牌窃取技术详解
  • A Survey on Efficient Large Language Model Training: From Data-centric Perspectives
  • 2026年7月最新沈阳雷达官方售后服务网点地址及客服电话一览 - 亨得利钟表维修中心
  • 宇舶官方更换表蒙价格查询|网点地址与24小时客服热线权威信息公告(2026年7月最新) - 亨得利官方服务中心
  • Node-RED实战:构建多角色权限登录与界面切换系统
  • 手机和电脑通用的拼图攻略,零基础也能上手 - 软件工具教程方法
  • 从.NET授权机制到dnSpy工具:深入解析软件保护与逆向分析原理
  • Qt学习(开发环境Visual Studio)5.工程结构介绍
  • 雷达官方服务项目及价格查询|详细地址与售后热线权威信息通告(2026年7月最新) - 亨得利钟表维修中心
  • css讲解
  • 锂电池组电压平衡方案:PIC18F86K90与MCP3202实战
  • Golang RSA加密实战:从密钥生成到生产环境避坑指南
  • PIC18F2620与CMT-8540S-SMT蜂鸣器嵌入式开发指南
  • 海外发稿报价不透明?媒介易让每笔费用都列在明处
  • request.getRequestDispatcher().forward() 、<jsp:forward>、response.sendRedirect()三者的区别
  • 2026年7月最新南通芝柏官方售后服务热线与网点地址查询 - 亨得利官方服务中心
  • 元初混沌 6G 全域通感一体化体系架构 第一卷三阶第二十七篇 场象:小区全域电磁笼罩梯度分布规律
  • 数据结构 - 线性表第三篇:基于顺序表实现 C 语言通讯录(基础功能篇)
  • 为什么大型组织都在从公有云IM转向私有化协作平台
  • 2026年7月最新无锡雅典官方售后热线及客户服务网点地址 - 亨得利官方服务中心
  • Spark图计算实战:GraphX与GraphFrames选型、分区与内存优化
  • 2026最新盘点:10款高效率写小说软件亲测,ai写小说哪个更好用?
  • 2026 年更新:峡江靠谱的回收平台哪个好,扔掉前先看一眼,这笔“隐形存款”正被你当垃圾处理 - 鉴选官
  • Apriori算法与关联规则挖掘实战指南
  • OpenGL运行环境搭建学习00篇【Related to https://learnopengl-cn.github.io/】
  • Normative Reasoning in Large Language Models: A Comparative Benchmark from Logical and Modal Pers...
  • 在浏览器中对视频mp4与背景音乐mp3都同步自动播放画面和声音