ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

鸿蒙开发Day1:Dart变量与基本类型,用Flutter逻辑实现简易计算器

鸿蒙开发Day1:Dart变量与基本类型,用Flutter逻辑实现简易计算器 今天是正式接触鸿蒙开发的第一天。我给自己定的路线不是走 ArkTS 官方那套而是先把 Flutter 这个跨端框架拉过来看看它到底能不能在鸿蒙生态里真正落地。标题里说的变量与基本类型和简易计算器逻辑就是我在 Day 1 给自己安排的目标不贪多不画大饼用最短的时间把 Dart 的基础语法过一遍再用一个足够小的项目把语法串起来。这个简易计算器不涉及 UI 渲染只做纯粹的逻辑层实现——这样做的原因很简单逻辑层可以脱离界面独立验证用单元测试就能兜底等后面接鸿蒙设备的 UI 时再去做真正麻烦的部分。如果你是打算在鸿蒙上做跨端应用、又不想放弃 Flutter 生态的老 Flutter 人或者你本身就是个零基础新手想找一条相对平滑的路先把 Dart 变量和类型搞懂这篇内容都值得你看下去。我会把选型思路、语法讲解、完整代码、单测用例、还有我踩到的坑全部摊开。1. 为什么我会选择在鸿蒙生态里先学 Flutter1.1 鸿蒙开发的路不止 ArkTS 一条鸿蒙系统的第一语言是 ArkTS这一点毋庸置疑。但我观察到的实际情况是并不是所有团队都有充足的时间去把整个团队的技术栈切到 ArkTS 上也不是所有业务场景都绝对需要原生实现。这个时候跨端框架作为第二个选项就非常有吸引力。Flutter 之所以能成为这个第二选项核心是这个框架本身已经积累了大量成熟组件和优秀的渲染引擎。它的多端支持能力并不只局限于 Android 和 iOSOpenHarmony 社区这边已经有对应的适配 SDK可以让 Flutter 应用跑在鸿蒙设备上。我 Day 1 选择的路径就是用 Flutter 的语言 Dart 去写业务逻辑再通过适配层的桥接把它部署到鸿蒙系统上。这个路子的优点在于代码资产可以留存将来你做安卓端、iOS 端也能复用同一套 Dart 逻辑不需要每个端写一遍。这里我不打算去贴什么夸大其词的结论。就我实际操作的感受来说鸿蒙生态的 Flutter 适配还处在能用但不够丝滑的阶段。比如后续你在跑 flutter run 的时候可能会遇到 gradle 配置、依赖导入之类的问题这些我在第 5 章会专门梳理。但如果你只是先把逻辑层写好、用单测验证好那是完全不受影响的。1.2 Day 1 的目标为什么选变量基本类型和计算器我见过太多人学跨端开发第一天就恨不得写一个完整的聊天 App结果第二天就被组件嵌套和状态管理的问题劝退了。Day 1 的正确打开方式应该是把编程语言的最小知识闭环打通。简单说就是——变量怎么声明、基本类型怎么区分、类型之间怎么转换然后拿这些底子去做一个稍微有一点业务含义的东西。简易计算器就是那个刚刚好的东西。它天然包含四则运算涉及数字、字符串、布尔值这些基本类型的来回切换还锻炼你设计状态的能力你按下数字 1、2、3 的时候当前输入值是字符串拼接还是数值相加按加减乘除时要不要保存第一个操作数按等于的时候怎么决定下一次按数字是开启新的一轮输入还是继续拼接这些全都是逻辑层面的决策任何一门语言都绕不开。把这一套想明白了变量与基本类型这一课才算真正毕业。2. 变量与基本类型Dart 这门语言的底色2.1 声明变量的三种姿势var、final、constDart 里声明变量的方式很灵活但很多新手一开始最容易搞混的就是 var、final、const 三者的区别。我直接用一句话概括它们的定位var 是类型推断 可重新赋值final 是只能赋值一次const 是编译期就确定的常量。var适合那些初始值明确、后面还可能变化的值。比如var currentInput ;它可以先给空字符串然后每次按键动态拼接新字符。final用于那些运行时才能确定、但确定后就不该变的值。比如一个计算器实例初始化后绑定的事件源用 final 标记能防止后续代码误改。const用在真正不可变的东西上。比如计算器按键的类型枚举集合或者一个固定不变的字符串常量都可以用 const 声明。这里有个比较容易踩的坑很多人以为var声明的变量就是动态类型其实完全不是。Dart 的var一旦初始化推断出的类型就固定了。你写了var count 0;它其实已经被推断为int后面再给它赋值字符串编译器会直接报类型不匹配不会像 JavaScript 那样替你偷偷转换。2.2 核心基本类型全家桶int、double、String、bool计算器逻辑里最频繁接触的四个类型是 int、double、String、bool。它们各自的定位和用法值得花几分钟过一遍。int是整数类型Dart 的 int 在原生 VM 上通常映射为 64 位整数在 Web 上则是 JS 的 number 概念。如果你的计算器涉及输入 3 然后按 5这个前导零的处理就需要 int 逻辑参与判断。double是浮点类型处理除法和小数结果时必不可少。Dart 里的10 / 3拿到的结果类型是 double这和很多语言一致。String是字符串类型在计算器里承担临时输入缓冲的角色你按的每个数字键如果要拼出一个完整的数字最自然是先把单个数字字符追加到字符串里等运算符到达时再进行数值解析。bool简单直白true/false 两个值用来标识当前是正在输入数字还是刚按完运算符。这四种类型组合起来基本上就能覆盖一个简易计算器 90% 的需求。剩下那 10%比如处理除以 0的异常边界我会放到第 5 章单独说。2.3 类型转换和几个影响计算的细节变量类型搞清楚了紧接着要面对的是类型转换。计算器逻辑里典型的场景是用户输入的是字符串 12但你计算时需要的是数字 12。Dart 里做这种转换有标准姿势String input 12; int number int.parse(input); // 转成整数 double decimal double.parse(input); // 转成浮点反过来想拼回去显示的时候用字符串插值更优雅比如$number或者${number.toString()}。这里有几个细节会直接影响计算器正确性我单独拎出来强调int.parse遇到空字符串或者 3.14 这种含小数点的字符串会直接抛异常所以做转换前必须先校验当前输入是否合法。除法符号在 Dart 里比较特殊/永远得到 double~/才是整除求商%是取余。计算器做普通四则运算时用/就好。浮点计算会有精度问题比如0.1 0.2在 Dart 里同样可能得到0.30000000000000004。简易计算器阶段可以接受这种小误差后续如果做专业计算器就要引入 Decimal 之类的库做定点运算了。3. 简易计算器逻辑从需求到实现3.1 先圈定功能范围避免一步登天写代码之前我习惯先画功能边界。这个 Day 1 的简易计算器我圈定的范围是支持加、减、乘、除四个操作支持连续运算比如 12 3 - 5支持按等号得到结果支持清空复位。其他像括号、百分比、正负号翻转、历史记录这些全部砍掉。砍掉不是逃避而是让核心逻辑保持足够简单方便你做单元测试定位问题。如果一开始就把括号优先级、符号翻转、连续等号这些功能全部塞进来那你面对的就不仅仅是一个变量与基本类型的练习而是一个编译原理相关的表达式求值问题那显然超出了 Day 1 的合理范围。3.2 用枚举和状态变量描述计算器内部状态一个计算器的核心状态其实可以用三个变量描述清楚第一个操作数、当前运算符、当前输入缓冲。这里我选择用 enum 来定义运算符和按键类型因为它比字符串常量更安全拼写错误在编译期就能暴露。enum CalcOperator { add, subtract, multiply, divide, none; } enum BtnType { digit, operator, equals, clear; }状态变量我设计成下面这套_firstOperand保存第一个操作数double 类型。_currentInput保存当前正在输入的数字字符串初始为空字符串。_currentOp保存当前选中的运算符默认CalcOperator.none。_isNewInput标志位判断用户下一次按数字时是接在当前输入后面还是开启新的输入。这组设计的核心思路是把用户看到的显示内容和内部真正的数值状态分开。用户按数字时界面可能显示一串字符串文本但真正参与计算时才把字符串转成 double。这样可以避免四则运算过程中反复做类型转换带来的混乱。3.3 核心逻辑数字键、运算符键、等号键三种响应路径计算器的所有行为本质上就是回答按下的这个键该怎么改状态。我把三种按键路径分别说明。按下数字键的时候如果_isNewInput为 true说明前面刚按完运算符或等号这时候要清空旧输入开始记录新数字否则就把数字字符追加到当前输入。同时需要注意前导零和单点规则比如不能出现 007也不能在一个数字串里出现两个小数点。按下运算符键的时候如果当前没有正在输入的值就用上一次的计算结果继续运算如果当前有值就以当前值为第二操作数进行一次暂存计算。简易计算器的做法是不等用户按等号就先算一次中间结果再把这个中间结果作为下一次运算的第一操作数。这样做的好处是支持连续运算坏处是更严谨的计算器需要区分链式计算和后置运算的语义这在 Day 1 不做深究。按下等号键的时候如果第一操作数和当前输入都存在直接执行对应的运算逻辑把结果写回第一操作数同时把_isNewInput置为 true表示下一次按数字就开始新的一轮输入而不是往结果后面拼接数字。这套流程看着简单真正把它写对、能把各种边界情况测出预期结果才算过了变量与基本类型这一关。4. 实操过程从零搭好 CalculateLogic 并用单测验证4.1 环境准备创建 Flutter 项目并确认鸿蒙支持我实操时没有一开始就接鸿蒙 SDK因为逻辑层可以脱离平台运行。我的建议是分两步走第一步先在本机装好 Flutter SDK通过flutter create创建一个最小项目第二步在确认普通 Dart 逻辑和单测没问题之后再去适配鸿蒙构建链。flutter create harmony_calculator cd harmony_calculator创建完项目以后如果你打算尝试跑鸿蒙真机就需要确认你用的 Flutter SDK 是否带 OpenHarmony 适配能力。社区里常见的做法是下载带 HarmonyOS 支持的 Flutter SDK 版本然后在项目里配置对应的构建标志。不过这一块目前版本迭代比较快我建议你以自己本机实际环境为准。这里有个容易忽略的点Flutter 项目本身是一个多平台项目鸿蒙只是其中一个目标平台。所以你在写 Dart 逻辑时完全不用关心平台差异把注意力集中在算法和状态管理上就行。这也是跨端框架的最大红利。4.2 计算器核心逻辑完整实现我直接给出一个经过单测验证的calculator_logic.dart完整版本。这个类被设计为不依赖任何 Flutter 组件所以可以脱离 UI 单独测试也可以后续被 UI 层直接调用。// lib/calculator_logic.dart enum CalcOperator { add, subtract, multiply, divide, none; } enum BtnType { digit, operator, equals, clear; } class CalculatorLogic { double _firstOperand 0; String _currentInput ; CalcOperator _currentOp CalcOperator.none; bool _isNewInput true; String get displayText _currentInput.isEmpty ? 0 : _currentInput; void handleDigit(String digit) { if (_isNewInput) { _currentInput ; _isNewInput false; } // 防止出现多个小数点 if (digit . _currentInput.contains(.)) { return; } // 防止前导零例如 007 if (_currentInput 0 digit ! .) { _currentInput digit; return; } _currentInput digit; } void handleOperator(CalcOperator op) { if (_currentInput.isNotEmpty) { double currentValue double.parse(_currentInput); if (_currentOp ! CalcOperator.none) { double result _perform(_firstOperand, currentValue, _currentOp); _firstOperand result; _currentInput _formatResult(result); } else { _firstOperand currentValue; } } _currentOp op; _isNewInput true; } void handleEquals() { if (_currentOp CalcOperator.none) return; if (_currentInput.isEmpty) return; double currentValue double.parse(_currentInput); double result _perform(_firstOperand, currentValue, _currentOp); _currentInput _formatResult(result); _firstOperand result; _currentOp CalcOperator.none; _isNewInput true; } void clear() { _firstOperand 0; _currentInput ; _currentOp CalcOperator.none; _isNewInput true; } double _perform(double a, double b, CalcOperator op) { switch (op) { case CalcOperator.add: return a b; case CalcOperator.subtract: return a - b; case CalcOperator.multiply: return a * b; case CalcOperator.divide: if (b 0) { // 简易计算器里除以零直接返回 0视需求可改成报错 return 0; } return a / b; case CalcOperator.none: return b; } } String _formatResult(double value) { if (value value.roundToDouble()) { return value.toInt().toString(); } return value.toString(); } }这套实现有几个设计细节值得注意。第一_formatResult的作用是避免结果出现1.0这种多余的显示整数结果直接显示1非整数才显示带小数的字符串。第二clear()不用重置为某个神奇初值直接把所有状态恢复默认即可。第三handleOperator里做了链式计算支持如果已经有当前运算符先算一次中间结果再存为新第一操作数。4.3 用单元测试把计算器逻辑钉死逻辑写得好不好单测说了算。Flutter 项目里跑 Dart 单测非常简单直接在test目录写测试文件就行。// test/calculator_logic_test.dart import package:flutter_test/flutter_test.dart; import package:harmony_calculator/calculator_logic.dart; void main() { group(CalculatorLogic Basic, () { test(12 3 15, () { final calc CalculatorLogic(); calc.handleDigit(1); calc.handleDigit(2); calc.handleOperator(CalcOperator.add); calc.handleDigit(3); calc.handleEquals(); expect(calc.displayText, 15); }); test(连续运算 12 3 - 5 10, () { final calc CalculatorLogic(); calc.handleDigit(1); calc.handleDigit(2); calc.handleOperator(CalcOperator.add); calc.handleDigit(3); calc.handleOperator(CalcOperator.subtract); calc.handleDigit(5); calc.handleEquals(); expect(calc.displayText, 10); }); test(除零不崩溃, () { final calc CalculatorLogic(); calc.handleDigit(8); calc.handleOperator(CalcOperator.divide); calc.handleDigit(0); calc.handleEquals(); expect(calc.displayText, 0); }); test(小数相加显示正确, () { final calc CalculatorLogic(); calc.handleDigit(1); calc.handleDigit(.); calc.handleDigit(5); calc.handleOperator(CalcOperator.add); calc.handleDigit(2); calc.handleDigit(.); calc.handleDigit(5); calc.handleEquals(); expect(calc.displayText, 4); }); }); }执行flutter test四个用例全部通过。这一步做完说明变量与基本类型这一课的理论基础已经有了一个可验证的落点。我强烈建议你不管做任何小功能都先养成为逻辑写单测的习惯。等后面把 UI 接上来你的调试成本会直线下降因为大部分数学错误在逻辑层就已经被卡掉了。5. 常见问题与排查实录5.1 flutter 新建项目后跑不起来这个热搜词我天天见。实际上新建项目跑不起来九成不是 Dart 代码的问题而是 Flutter 工具链和本地环境之间的矛盾。最常见的情况flutter pub get网络不稳定时半途失败或者 Android 相关的 Gradle 配置下载不了依赖导致链式构建整体卡死。我在排查时会按顺序执行三件事先看flutter doctor -v确认 Flutter SDK、Dart SDK、编辑器插件三个核心链路的版本号是否匹配。然后再跑一次flutter clean清理掉缓存的构建产物有些旧索引残留会把新项目卡住。最后检查 Android toolchain 的 license 是否接受没有 accept 的话直接报错界面提示会让你看得很懵。如果你用的是 OpenHarmony 适配的 Flutter SDK还要多确认一步项目里是否引入了正确的鸿蒙平台工程配置否则flutter run找不到目标设备的平台通道。这些不是逻辑问题但会消耗大量时间所以我建议在动手写代码前先把空项目的构建链路跑通。5.2 遇到 Impeller 渲染引擎报错怎么办另一个高频词是 flutter impeller。Impeller 是 Flutter 新一代渲染引擎它的目标是用更现代的方式解决旧 Skia 渲染管线中的卡顿和帧率波动问题。但在部分鸿蒙设备或模拟器上Impeller 的兼容性并不完美跑起来以后可能看到类似这样的日志E/flutter (31173): [error:flutter/runtime/dart_vm_initializer.cc(41)] unhand...这种报错很多时候并不是你的计算器逻辑有问题而是渲染管线在目标设备上初始化失败。排查思路非常简单先去留意报错日志的前半段有没有 Vulkan 或者 Impeller 相关字样如果有可以尝试临时切回 Skia 渲染模式跑通流程。这属于环境适配问题不影响 Dart 业务逻辑的正确性。在 Day 1 阶段我甚至建议你先不关心 UI 渲染直接flutter test跑单测就行。这样可以完全绕过渲染引擎的问题先把逻辑层做扎实再回头处理设备侧适配。5.3 计算器边界情况很难一次写对我在写这个计算器逻辑时真正踩到的坑不在于四则运算本身而在于状态机的边界。比如用户快速按了两下等号怎么办用户按完运算符后立刻按开根号怎么办用户输入 0.5 时前导零怎么处理。这些边界情况如果不通过单测覆盖等接到 UI 上就是一堆闪退和奇怪结果。我自己整理了一个边界速查表现在分享给你场景预期行为处理方式连续输入 007显示 7前导零直接忽略输入 1.2.3保持 1.2检测到第二个小数点时忽略按完运算符后按等号不执行计算当前输入为空时直接 return除数为 0不崩溃返回 0 或抛错按产品需求决定连续运算 12 3 - 5先算 123再算 15-5运算符到达时先算中间结果这张表看着简单每一条背后都是一个真实的调试经历。我建议你拿到代码后亲自把这张表所有场景写成单测跑一遍这样对计算器状态机的理解会非常扎实。5.4 关于组件通信和状态管理工具的误区热搜词里还有 flutter 组件通信和 flutter provider 怎么用这两个东西在 Day 1 阶段其实可以暂时不碰。组件通信解决的是跨组件数据共享问题比如按钮点击之后要刷新顶部显示区域的文本。Provider 则是把状态管理和 UI 自动重建绑定在一起的框架属于后续进阶内容。很多新手学 Flutter 上来就先学一堆状态管理库结果连最基本的变量和类型转换都没搞明白这是本末倒置。我的建议是先把计算器的逻辑层写成普通的类方法通过最简单的函数调用拿到结果等真的遇到多个页面共享一个状态时再引入 Provider、Riverpod 这些工具。到那时候你已经对状态流转有清晰感知了用工具就是水到渠成的事。6. 真机适配前必须清楚的几个底层认知6.1 鸿蒙上的 Flutter 到底是怎么跑起来的理解这个问题对后续调试帮助很大。标准 Flutter 应用在 Android 上是通过 flutter engine 嵌入到 Android Activity 里的渲染由 Skia 或 Impeller 完成。如果你要跑鸿蒙适配层做的事情本质上是让 Flutter engine 能够在鸿蒙的应用框架中创建一个窗口承载层由鸿蒙系统负责窗口、触摸、生命周期事件Flutter 自己负责业务逻辑和绘制。这意味着你写的 Dart 代码在鸿蒙上执行的引擎内核和 Android 上其实是同一个所以计算器逻辑这种纯 Dart 模块可以原样复用到鸿蒙应用里。需要重新适配的只是平台桥接和构建配置。我觉得这个认知特别重要它决定了你的学习路径逻辑层精力可以投入多一点因为这一部分的投资能跨多个平台复用平台适配层精力投入要谨慎按需处理即可。6.2 调试设备连接与构建链路热搜词里还有类似非华为电脑连接鸿蒙手机的疑问。我自己实测的感觉是鸿蒙设备的调试连接确实不能完全照搬 Android 的 adb 思路因为它的底层通道协议不一样。连接调试设备前你得先确认电脑端的 SDK 工具包版本和设备系统版本匹配否则拔插多少次都没反应。这里我能给的建议不多因为不同版本的鸿蒙和不同品牌的电脑连接细节差异很大。我倾向于在 Day 1 阶段建议读者先用纯 Dart 单元测试跑通逻辑再尝试连接真机。这样可以逐步引入变量不会因为一个环境问题把学习节奏打乱。6.3 规划 Day 2 到 Day 7 的路线Day 1 的变量与基本类型和计算器逻辑只是第一块基石。接下来我给自己排的路线大概是Day 2 用 Flutter 的 Widget 把计算器界面搭出来只做最简单的文本按钮Day 3 把逻辑层和 UI 层通过状态管理串联Day 4 处理运算符优先级和连续等号进阶问题Day 5 再回到鸿蒙构建链路把应用真正跑上真机。这样的节奏每一步的复利都积累在前一步之上。如果只想着一口气写完一个完整计算器 App大概率会在第 2 天就卡在 UI 细节上反而丢了主线。最后再分享一个小技巧我所有 Day 1 的代码都有一个共性习惯每个公开方法都保持足够小小到可以单独测试。这个习惯是从当年写后端接口上传下来的但用在计算器状态机上同样有效。状态机的流转逻辑本质上就是一系列输入对状态的迁移路径每一段路径都可以是一条测试用例。当你后续接上鸿蒙真机界面点击事件触发的是同一个迁移函数单测覆盖过的那部分逻辑你会非常有信心。根据我个人的经验跨端开发最贵的不是写代码而是排查为什么我机器上没问题、换台设备就翻车的问题。逻辑层越可靠这种返工就越少。今天就分享这些下一期我会接着写 Flutter 的基础组件和计算器 UI 层的组装。
返回列表