实验5:鸿蒙开发入门及计算器开发
姓名:周洋迅 学号:24020007175
| 姓名和学号? | 周洋迅,24020007175 |
|---|---|
| 本实验属于哪门课程? | 中国海洋大学26夏《移动软件开发》 |
| 实验名称? | 实验5:鸿蒙开发入门及计算器开发 |
| 博客地址? | https://blog.csdn.net/2401_85763240/article/details/164459773 |
| 代码仓库地址? | GitHub - zysgusg/mobileDev: 本仓库记录个人在2026夏季学期《移动软件开发》的课程实验。课程以移动应用开发基本编程能力培养为目标,突出小程序基础知识的讲授,并结合实践案例及项目,使学习者从无到有,逐步掌握微信小程序的基本开发技能,为微信小程序的高级开发打下坚实基础。今年新增设鸿蒙实验环节。 · GitHub |
一、实验目的
-
在 Windows 上安装 DevEco Studio 并创建鸿蒙模拟器,掌握 ArkTS 工程的创建、编译构建以及应用在模拟器上的安装与运行。
-
按照华为官方架构指南实现一个简易计算器,掌握 ArkUI 声明式开发(
@Entry/@Component/@State/@StorageLink/@Builder等)、页面路由、自定义键盘与表达式求值等核心技术。 -
在教程计算器基础上进行个性化创新开发,做出带有个人想法与风格的计算器作品。
二、实验内容
基于 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 × e,2e10表示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.onWindowStageCreate中loadContent('pages/HomePage')加载主页面; -
main_pages.json注册两个页面:pages/HomePage与pages/ScientificCalculator。
3.2 简易计算器:键盘数据与界面布局
键盘按键用数据模型 PressKeyItem(flag 区分类型: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内嵌Column的ForEach渲染,键盘数据中的每组按键竖排成一列、共 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 简易计算器:输入校验与表达式求值
运算表达式保存在 expressions(Array<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×e,2e10=2×10¹⁰); -
函数求值:
sin/cos/tan先把角度转弧度(×π/180,即按键以角度制输入),log为常用对数log10,ln为自然对数,√为开方; -
错误与溢出处理:除零主动抛错、括号不匹配等异常统一在
evaluate入口捕获返回'NaN';结果非有限或|结果|>1e15返回'OVERFLOW',页面一律显示error; -
结果格式化:整数直接输出;非整数用
toPrecision(12)截断并去除末尾多余的0与小数点。
输入法直输由 handleTextInput 承接:TextArea.onChange 收到的原始文本先经 parseInputString 重新切分为 token(兼容全角 +-×÷()、*///^、pi、sin 等字母函数名、后缀 ²³),末尾是 = 时自动求值提交,使键盘点按与系统键盘键入走同一套解析与预览逻辑,不会出现两套行为不一致的问题。
四、实验结果
以下在 DevEco Studio 模拟器(Phone)上编译运行验证:
-
主计算器页面:深蓝紫底 + 玻璃拟态按键的霓虹风格界面,标题栏右侧为「科学」入口;输入
1,234+5,678过程中上方表达式自动加千分位,下方实时显示结果。
-
科学函数验证:
sin(90)=1(角度制)、√(9)=3、x²/xⁿ幂运算、π/e常数代入正确;如2^3^2=512验证^右结合,2e与2e10区分正确。
-
异常处理:除零、
log(0)、结果超过1e15(如1e10 × 1e10)时结果区显示error而非Infinity。
五、问题与解决方案
-
欧拉常数
e与科学计数法冲突:同一字符在表达式中既可能表示常数(2e应为2×e)又可能是科学计数法指数(2e10应为2×10¹⁰),若一律把e当指数会产生歧义。解决方案:词法分析时只把“e/E后紧跟(可选正负号 +)数字”并入数字 token,独立的e单独成 token、解析为Math.E,从词法层消除冲突。 -
x²/x³后缀幂与^的嵌套问题:最初尝试把平方键实现为“插入^2”,但这样9²²、(2+1)²等写法要么无法连续叠加、要么作用范围错误。方案:输入侧handlePower只允许把后缀幂贴在数字、已带幂后缀的项或)上,允许链式(9²²=(9²)²);语法侧后缀幂放在parseUnary层、紧贴Primary作用,优先级高于×÷,使2+3²先算3²,与手写数学记法一致。 -
非法计算的结果表示:直接求
1÷0会得到Infinity,大数连乘可能溢出为不直观的长串数字。方案:除零等异常在解析器内抛出并被统一捕获返回NaN;同时对结果做有限性与量级检查(|结果|>1e15判为OVERFLOW),页面统一显示error文案,杜绝Infinity/NaN裸奔到界面。 -
沉浸式全屏后按键被系统条遮挡:设置全屏布局后最底部一行按键与
=大键会伸到导航条之下无法点按。方案:通过getWindowAvoidArea获取TYPE_SYSTEM/TYPE_NAVIGATION_INDICATOR两区高度写入AppStorage,页面@StorageLink绑定后对标题栏、键盘区分别加margin-top/padding-bottom避让,并监听avoidAreaChange保证旋转后依然正确——新增科学页时同样绑定这两个变量即可复用。 -
科学页按键多、列难对齐:函数区与数字区若分开排布,两区列宽极易错位。方案:统一用 5 列
columnsTemplate('1fr ...'),GridItem按columnStart / rowStart / colSpan / rowSpan精确落位(=跨 4 行、0跨 2 列),整页按键天然对齐且新增键只需改数据模型。 -
系统键盘键入与点按两套逻辑不一致:允许 TextArea 获得焦点后直接打字后,若另写一套求值逻辑极易与按键行为产生差异。方案:键入文本统一先经
parseInputString切回与点按相同的 token 序列(expressions),再走同一条实时预览 / 求值管线,=结尾自动提交,保证两种输入方式结果一致。
六、实验总结
本次实验我完整走通了鸿蒙应用开发全流程:安装 DevEco Studio、创建并启动 Phone 模拟器、编译推包运行 ArkTS 工程;按官方架构指南实现了具备表达式校验、优先级计算、实时预览、连算、千分位与科学计数法格式化的简易计算器,理解了“数据模型(键盘键位)— 业务逻辑(表达式数组 + 调度场求值)— 视图(ArkUI 声明式布局)”的分层思路,以及 @State 驱动 UI 刷新、@StorageLink 跨页面共享安全区数据等响应式机制。
在个性化环节,我不仅为主计算器重新设计了霓虹深色玻璃拟态主题,还扩展了一整页科学计算器:用递归下降解析器支持了函数、括号、幂运算与常数,处理了欧拉数与科学计数法的词法歧义、^ 右结合、后缀幂嵌套等易错细节,并让按键与系统键盘键入共用同一套求值逻辑。通过解决全屏避让、布局对齐、溢出表达等问题,我对鸿蒙页面路由、Grid 布局与窗口适配也有了更深的体会,整体达成了本次实验的目标。
更多推荐



所有评论(0)