姓名:周洋迅 学号:24020007175

姓名和学号?周洋迅,24020007175
本实验属于哪门课程?中国海洋大学26夏《移动软件开发》
实验名称?实验5:鸿蒙开发入门及计算器开发
博客地址?https://blog.csdn.net/2401_85763240/article/details/164459773
代码仓库地址?GitHub - zysgusg/mobileDev: 本仓库记录个人在2026夏季学期《移动软件开发》的课程实验。课程以移动应用开发基本编程能力培养为目标,突出小程序基础知识的讲授,并结合实践案例及项目,使学习者从无到有,逐步掌握微信小程序的基本开发技能,为微信小程序的高级开发打下坚实基础。今年新增设鸿蒙实验环节。 · GitHub

一、实验目的

  1. 在 Windows 上安装 DevEco Studio 并创建鸿蒙模拟器,掌握 ArkTS 工程的创建、编译构建以及应用在模拟器上的安装与运行。

  2. 按照华为官方架构指南实现一个简易计算器,掌握 ArkUI 声明式开发(@Entry / @Component / @State / @StorageLink / @Builder 等)、页面路由、自定义键盘与表达式求值等核心技术。

  3. 在教程计算器基础上进行个性化创新开发,做出带有个人想法与风格的计算器作品。

二、实验内容

基于 HarmonyOS 的 ArkTS 语言完成两款深色霓虹风格的计算器:

开发环境搭建:安装 DevEco Studio、创建本地模拟器,并通过运行 Hello World 工程验证环境可用。

简易计算器(主页面 HomePage):参考华为《计算器》架构指南实现:

  • 自定义键盘(键位数据分四组 C 7 4 1 %÷ 8 5 2 0× 9 6 3 .del − + =,页面中每组竖排成一列渲染):数字 0~9、小数点 .、百分号 %、四则运算 + − × ÷C 清空、 退格与 = 求值大键。

  • 表达式合法性校验:不允许 0 后直接跟数字、同一数字内重复小数点、% 不能作为首字符或紧跟在 之后等。

  • 运算过程实时预览结果(未按 = 即可看到中间结果);按 = 后将结果写入输入区,可继续参与下一次运算(连算)。

  • 运算采用「调度场算法」中缀转后缀求值,× ÷ 优先级高于 + −% 在求值前替换为 ÷100;浮点运算通过放大为整数再计算避免精度丢失;结果超长(>16 位)自动转为科学计数法。

  • 输入与结果显示自动加千分位分隔符。

个性化创新一 · 霓虹深色玻璃拟态主题:整体改为深蓝紫底(#1A1A2E)风格,运算符与结果用霓虹青 #00D4FF、功能键与 = 用霓虹粉 #FF6B9D、科学计算器入口用霓虹紫 #9B59B6;按键采用半透明玻璃质感(rgba 背景 + 细描边 + 柔和阴影),= 为粉色发光大键。

个性化创新二 · 科学计算器页面:主页面标题栏新增「科学」入口,通过路由 pushUrl 打开独立的科学计算器页:

  • 顶部 3×5 科学函数区:sin / cos / tan / log / ln / √ / x² / x³ / xⁿ / π / e / ( / ) / C / ⌫;底部数字键盘与主页面保持一致,= 跨 4 行、0 跨 2 列,两区共用 5 列模板保证按键列对齐。

  • 采用递归下降解析器求值,支持括号嵌套、函数、幂运算(^ 右结合)、后缀幂(x² x³ 可连续嵌套,如 9²²)、角度制三角函数、常数 π / 欧拉数 e

  • 区分欧拉常数 e 与科学计数法:2e 表示 2 × e2e10 表示 2×10¹⁰

  • 支持系统输入法直接键入表达式(含 pi*/、全角符号等),以 = 结尾回车即求值;按键与键入共用同一套解析逻辑。

  • 结果实时预览、千分位格式化、科学计数法保留;结果绝对值超过 1e15 或除零等非法计算统一显示 error

全屏沉浸式适配:应用窗口设置全屏布局,通过监听窗口避让区把状态栏 / 导航条高度写入 AppStorage,页面用 @StorageLink 动态留出上下安全区域,避免内容被系统条遮挡。

三、实验步骤

3.1 开发环境搭建与工程创建

按实验指导安装 DevEco Studio(Windows 11,内存 ≥16GB)并创建模拟器:在 Tools > Device Manager 中设置模拟器与系统镜像存储路径,下载 Phone 镜像后新建并启动模拟器实例。

随后创建新工程:选择 Empty Ability 模板,填写 Project name、包名(如 com.example.calculator,点号分隔不少于三段)、本地存储路径、Compatible SDK 等,模块默认生成 entry。工程同步完成后单击 Run > Run 'entry' 编译推包,应用即可运行在模拟器上,以此验收环境。

工程页面注册与入口配置:

  • EntryAbility.onWindowStageCreateloadContent('pages/HomePage') 加载主页面;

  • main_pages.json 注册两个页面:pages/HomePagepages/ScientificCalculator

3.2 简易计算器:键盘数据与界面布局

键盘按键用数据模型 PressKeyItemflag 区分类型:0 功能键、1 数字、2 运算符、3 等号)描述,由 PresskeysViewModel 统一提供 4 行数据:

 // row0:C 7 4 1 %;row1:÷ 8 5 2 0;row2:× 9 6 3 .
 // row3:del − + =(= 号在列底端占据 148vp 高的大键位)
 new PressKeyItem(2, '28vp', '37vp', 'C'),
 new PressKeyItem(1, '26vp', '35vp', '7'),
 new PressKeyItem(1, '26vp', '35vp', '4'),
 // …… 详见 viewmodel/PresskeysViewModel.ets

页面自上而下分为三块:标题栏、显示区、键盘区(.layoutWeight(1) 撑满剩余空间)。

  • 显示区:上部分多行 TextArea 展示输入表达式(48fp 右对齐、随输入自动换行),下部分霓虹青 Text 实时展示计算结果;

  • 键盘区:用 Row 内嵌 ColumnForEach 渲染,键盘数据中的每组按键竖排成一列、共 4 列并排,列间距 SpaceBetween;列内按键高 64vp 等距分布,末列底部的 = 号加高至 148vp、圆角 40 并带粉色光晕阴影,突出主操作键。

主题通过“类型对应配色”实现(见 HomePage.getGlassBg / getBorderColor):数字键为 rgba(255,255,255,0.06) 半透明白玻璃,运算符键为霓虹青 12% 透明底 + 青色描边,功能键为霓虹粉 10% 透明底,整体叠在 #232340 半透明键盘面板上,形成玻璃拟态质感:

 if (keyItem.flag === 2) {            // 运算符:霓虹青
   return 'rgba(0, 212, 255, 0.12)';
 } else if (keyItem.flag === 0) {     // 功能键:霓虹粉
   return 'rgba(255, 107, 157, 0.10)';
 }
 return 'rgba(255, 255, 255, 0.06)'; // 数字键:白色半透明玻璃
3.3 简易计算器:输入校验与表达式求值

运算表达式保存在 expressionsArray<string>)中,每个数字或运算符是独立元素,便于回退与解析。按键分发规则:功能 / 运算符 / 等号走 inputSymbol(),数字走 inputNumber()

  • validateEnter 输入校验:过滤非法输入(0 后不能直接跟数字、一个数中不能出现两个小数点、% 不能作为表达式开头或紧跟在 后、% 后不能再输入等);

  • inputOperators 运算符处理:支持表达式开头的负数( 可作一元符号入栈);连续输入运算符时后键替换前键,并清除冗余嵌套运算符,避免出现 1+×2 之类的非法式子;

  • inputDelete 退格:数字逐个删字符,删除到只剩运算符时整项出栈;

  • CalculateUtil.parseExpression 求值:先处理 %(如 50% 改写为 50÷100),再把中缀表达式用栈转后缀(乘除优先级高),最后用结果栈求出数值。浮点乘除先把小数部分按位数放大为整数运算再缩小,加减按最大小数位放大并以 toFixed 截断,避免 0.1×0.2 之类的浮点误差;结果超过 16 位时自动转为 aEn 科学计数法。

 async getResult() {
   let calResult = CalculateUtil.parseExpression(this.deepCopy());
   if (calResult === 'NaN') {
     this.calValue = this.resourceToString($r('app.string.error'));
     return false;
   }
   this.calValue = calResult;
   return true;
 }

实时预览与等号提交:每次输入数字后即调用 getResult() 刷新下方结果区;按 = 时把结果写回输入框(expressions 只保留结果一项),此后继续输入数字即开始新一轮计算,实现“连算”。

千分位格式化formatInputValue 用正则对表达式每一项、resultFormat 对结果做 1,234,567 式分组。

3.4 全屏沉浸式与系统避让区适配

EntryAbility.onWindowStageCreate 中把主窗口设为全屏布局,并通过 getWindowAvoidArea 读取状态栏(TYPE_SYSTEM)与导航条(TYPE_NAVIGATION_INDICATOR)高度写入 AppStorage,同时注册 avoidAreaChange 监听以在旋转 / 显隐变化时动态更新:

 windowClass.setWindowLayoutFullScreen(true);
 let type = window.AvoidAreaType.TYPE_NAVIGATION_INDICATOR;
 let avoidArea = windowClass.getWindowAvoidArea(type);
 AppStorage.setOrCreate('bottomRectHeight', avoidArea.bottomRect.height);
 // …… topRectHeight 同理,并监听 avoidAreaChange 动态刷新

页面内通过 @StorageLink 同步这两个值:标题栏 margin-top 让出状态栏高度,键盘区 padding-bottom 让出导航条高度,保证按钮不被系统手势条遮挡,且页面横竖屏切换后依旧正确。

3.5 科学计算器:页面与键位布局

主页面标题栏右侧新增「科学」胶囊按钮,getUIContext().getRouter().pushUrl({ url: 'pages/ScientificCalculator' }) 跳转;科学页左上角圆形「‹」返回键调用 back()

键盘区由一个 Scroll 包住两个 Grid,二者 columnsTemplate 均为 '1fr 1fr 1fr 1fr 1fr',保证上下按键列对齐:

 // 顶部 3×5 科学函数键:按 index 计算行列 (col=index%5, row=⌊index/5⌋)
 Grid() {
   ForEach(this.sciKeys, (keyItem: PressKeyItem, index?: number) => {
     GridItem() { this.buildKey(keyItem); }
       .columnStart((index ?? 0) % 5).columnEnd((index ?? 0) % 5)
       .rowStart(Math.floor((index ?? 0) / 5)).rowEnd(Math.floor((index ?? 0) / 5));
   }, ...);
 }
 .columnsTemplate('1fr 1fr 1fr 1fr 1fr').rowsTemplate('1fr 1fr 1fr')

底部数字键盘的每个键用 GridKeyDef 描述精确位置与跨距:= 位于第 5 列 rowSpan=4 贯通整列,0 占据前两列 colSpan=2,其余键常规占位。函数键统一 flag=4,配色使用霓虹紫,与主页面的青 / 粉体系共同构成整体视觉。

科学函数键的按键语义

  • πe:以希腊字母 / 字母形式直接入表达式,由解析器换算为 Math.PI / Math.E

  • sin/cos/tan/log/ln/√:自动补左括号,形如 sin(,等用户输入角度或算式;

  • x²/x³:后缀幂,作用于前一个数字、已带幂后缀的项或右括号(9²² 合法,相当于 (9²)²);xⁿ 则插入 ^ 运算符;

  • ( / ):自动配对校验,仅当左括号多于右括号时允许输入 )

  • 退格、C 清空与主计算器行为一致。

3.6 科学计算器:递归下降解析器与键入支持

新增 ScientificCalculateUtil 工具类,用递归下降方式实现完整表达式求值,文法层次为:

 表达式 Expression = Term (('+' | '-') Term)*
 项 Term        = Power (('×' | '÷') Power)*
 幂 Power       = Unary ('^' Power)?            // ^ 右结合:2^3^2 = 2^(3^2)
 一元 Unary     = ('+' | '-')? Primary ('²' | '³')*
 基本 Primary   = 数字 | π | e | '(' Expression ')' | 函数 '(' Expression ')'
  • tokenize 词法分析:数字扫描支持小数与科学计数法(1.5e10),但只有当 e/E 后紧跟(可选符号 + 数字)时才并入指数,否则字母 e 单独成 token,从词法层解决了“欧拉数”与“科学计数法”的歧义(2e = 2×e2e10 = 2×10¹⁰);

  • 函数求值sin/cos/tan 先把角度转弧度(×π/180,即按键以角度制输入),log 为常用对数 log10ln 为自然对数, 为开方;

  • 错误与溢出处理:除零主动抛错、括号不匹配等异常统一在 evaluate 入口捕获返回 'NaN';结果非有限或 |结果|>1e15 返回 'OVERFLOW',页面一律显示 error

  • 结果格式化:整数直接输出;非整数用 toPrecision(12) 截断并去除末尾多余的 0 与小数点。

输入法直输由 handleTextInput 承接:TextArea.onChange 收到的原始文本先经 parseInputString 重新切分为 token(兼容全角 +-×÷()*///^pisin 等字母函数名、后缀 ²³),末尾是 = 时自动求值提交,使键盘点按与系统键盘键入走同一套解析与预览逻辑,不会出现两套行为不一致的问题。

四、实验结果

以下在 DevEco Studio 模拟器(Phone)上编译运行验证:

  1. 主计算器页面:深蓝紫底 + 玻璃拟态按键的霓虹风格界面,标题栏右侧为「科学」入口;输入 1,234+5,678 过程中上方表达式自动加千分位,下方实时显示结果。

  2. 科学函数验证sin(90)=1(角度制)、√(9)=3/xⁿ 幂运算、π/e 常数代入正确;如 2^3^2=512 验证 ^ 右结合,2e2e10 区分正确。

  3. 异常处理:除零、log(0)、结果超过 1e15(如 1e10 × 1e10)时结果区显示 error 而非 Infinity

    五、问题与解决方案

  4. 欧拉常数 e 与科学计数法冲突:同一字符在表达式中既可能表示常数(2e 应为 2×e)又可能是科学计数法指数(2e10 应为 2×10¹⁰),若一律把 e 当指数会产生歧义。解决方案:词法分析时只把“e/E 后紧跟(可选正负号 +)数字”并入数字 token,独立的 e 单独成 token、解析为 Math.E,从词法层消除冲突。

  5. x²/x³ 后缀幂与 ^ 的嵌套问题:最初尝试把平方键实现为“插入 ^2”,但这样 9²²(2+1)² 等写法要么无法连续叠加、要么作用范围错误。方案:输入侧 handlePower 只允许把后缀幂贴在数字、已带幂后缀的项或 ) 上,允许链式(9²²=(9²)²);语法侧后缀幂放在 parseUnary 层、紧贴 Primary 作用,优先级高于 ×÷,使 2+3² 先算 ,与手写数学记法一致。

  6. 非法计算的结果表示:直接求 1÷0 会得到 Infinity,大数连乘可能溢出为不直观的长串数字。方案:除零等异常在解析器内抛出并被统一捕获返回 NaN;同时对结果做有限性与量级检查(|结果|>1e15 判为 OVERFLOW),页面统一显示 error 文案,杜绝 Infinity / NaN 裸奔到界面。

  7. 沉浸式全屏后按键被系统条遮挡:设置全屏布局后最底部一行按键与 = 大键会伸到导航条之下无法点按。方案:通过 getWindowAvoidArea 获取 TYPE_SYSTEM / TYPE_NAVIGATION_INDICATOR 两区高度写入 AppStorage,页面 @StorageLink 绑定后对标题栏、键盘区分别加 margin-top / padding-bottom 避让,并监听 avoidAreaChange 保证旋转后依然正确——新增科学页时同样绑定这两个变量即可复用。

  8. 科学页按键多、列难对齐:函数区与数字区若分开排布,两区列宽极易错位。方案:统一用 5 列 columnsTemplate('1fr ...')GridItemcolumnStart / rowStart / colSpan / rowSpan 精确落位(= 跨 4 行、0 跨 2 列),整页按键天然对齐且新增键只需改数据模型。

  9. 系统键盘键入与点按两套逻辑不一致:允许 TextArea 获得焦点后直接打字后,若另写一套求值逻辑极易与按键行为产生差异。方案:键入文本统一先经 parseInputString 切回与点按相同的 token 序列(expressions),再走同一条实时预览 / 求值管线,= 结尾自动提交,保证两种输入方式结果一致。

六、实验总结

本次实验我完整走通了鸿蒙应用开发全流程:安装 DevEco Studio、创建并启动 Phone 模拟器、编译推包运行 ArkTS 工程;按官方架构指南实现了具备表达式校验、优先级计算、实时预览、连算、千分位与科学计数法格式化的简易计算器,理解了“数据模型(键盘键位)— 业务逻辑(表达式数组 + 调度场求值)— 视图(ArkUI 声明式布局)”的分层思路,以及 @State 驱动 UI 刷新、@StorageLink 跨页面共享安全区数据等响应式机制。

在个性化环节,我不仅为主计算器重新设计了霓虹深色玻璃拟态主题,还扩展了一整页科学计算器:用递归下降解析器支持了函数、括号、幂运算与常数,处理了欧拉数与科学计数法的词法歧义、^ 右结合、后缀幂嵌套等易错细节,并让按键与系统键盘键入共用同一套求值逻辑。通过解决全屏避让、布局对齐、溢出表达等问题,我对鸿蒙页面路由、Grid 布局与窗口适配也有了更深的体会,整体达成了本次实验的目标。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐