:教程、主题与项目总结 — 从开发到上线的完整回顾)
鸿蒙原生应用实战五教程、主题与项目总结 — 从开发到上线的完整回顾前言这是本系列的最后一篇。我们将完成剩余两个页面——教程页面TutorialPage和自定义主题CustomThemePage然后对整个项目的架构、关键技术点和优化方向做全面总结。本篇内容交互式教程页面的步进设计7 步数独教学的内容规划主题系统的数据结构与实时预览8 套主题的 Grid 网格布局项目架构回顾与设计模式总结持续优化方向一、教程页面 — TutorialPage教程页面是新手引导的重要组成部分。我们设计了一个 7 步的教学流程覆盖从规则到技巧的完整学习路径。1.1 页面设计思路进度条 (7段) ┌────────────────────────────────────────┐ │ ← 返回 数独教程 2/7 │ ← 标题栏 ├────────────────────────────────────────┤ │ ▓▓▓▓▓▓░░░░░░░░░░░░░░░░░░░░░░░░░░░░ │ ← 进度条 ├────────────────────────────────────────┤ │ 第2步 │ ← 步骤编号 │ 基本规则 │ ← 步骤标题 │ │ │ ┌────────────────────────────┐ │ │ │ 规则一每行1-9各一次 │ │ ← 内容卡片 │ │ 规则二每列1-9各一次 │ │ │ │ 规则三每宫1-9各一次 │ │ │ └────────────────────────────┘ │ │ │ │ ┌────────────────────────────┐ │ │ │ 示例说明 │ │ ← 示例卡片 │ │ 行: 每行9个数字不重复 │ │ │ └────────────────────────────┘ │ │ │ ├────────────────────────────────────────┤ │ [上一步] [下一步] │ ← 导航按钮 └────────────────────────────────────────┘1.2 数据结构interfaceStepItem{step:number;// 步骤序号title:string;// 步骤标题content:string;// 详细内容example:string;// 示例说明}1.3 7 步教程内容第1步 — 认识数独 内容盘面是9×9网格 → 9个3×3宫格 → 填入1-9 示例用 ASCII 棋盘展示初始状态 第2步 — 基本规则 内容行规则 列规则 宫规则 提示数 示例文字形式描述三条规则 第3步 — 唯一余数法 内容观察行/列/宫 → 8个数字已出现 → 剩唯一数字 示例某空格行列宫的数字交集 第4步 — 摒除法 内容某数字在区域内只能放一个位置 示例数字1在左上宫只能放在中间格 第5步 — 笔记模式 内容候选数标记 → 逐步排除 示例格子可能填 2,5,8 → 逐步排除 第6步 — 游戏技巧 内容从易到难 → 逐数扫描 → 注意成对候选数 示例综合技巧运用 第7步 — 难度说明 内容简单(★☆☆) / 中等(★★★) / 困难(★★★★★) 示例各难度特点对比1.4 步骤状态管理StatecurrentStep:number0;// 当前步骤索引0开始privatesteps:StepItem[][/* 7个步骤 */];currentStep是唯一的State变量。每次用户点击上一步/下一步时修改它触发整个页面重新渲染。1.5 进度条实现教程顶部有一个 7 段进度条直观显示当前位置Row(){ForEach(this.steps,(step:StepItem,index:number){Column().layoutWeight(1).height(4).backgroundColor(indexthis.currentStep?$r(app.color.primary)// 已完成的步骤主题色:$r(app.color.divider)// 未完成的步骤灰色).margin({left:2,right:2}).borderRadius(2)},(step:StepItem)step.step.toString())}.width(90%).margin({bottom:16})关键设计点index this.currentStep当前步及之前的已完成之后的未完成layoutWeight(1)7 段均分宽度段之间用 2vp 间距分隔1.6 内容卡片Column(){Text(this.steps[this.currentStep].content).fontSize($r(app.float.body_font_size)).fontColor($r(app.color.text_secondary)).lineHeight(24)}.width(100%).padding(16).backgroundColor($r(app.color.card_bg)).borderRadius($r(app.float.card_corner_radius)).margin({top:16})示例卡片使用monospace字体等宽字体适合展示 ASCII 棋盘Column(){Text(示例说明).fontSize($r(app.float.body_font_size)).fontWeight(FontWeight.Medium).fontColor($r(app.color.text_primary)).margin({bottom:12})Text(this.steps[this.currentStep].example).fontSize($r(app.float.small_font_size)).fontColor($r(app.color.text_secondary)).fontFamily(monospace)// 等宽字体.lineHeight(22)}.width(100%).padding(16).backgroundColor($r(app.color.card_bg)).borderRadius($r(app.float.card_corner_radius)).margin({top:12})1.7 导航按钮的条件渲染底部按钮根据当前步骤动态变化Row(){// 上一步按钮只在第1步之后显示if(this.currentStep0){Button(上一步).height(44).backgroundColor($r(app.color.background)).borderRadius(22).fontColor($r(app.color.text_primary)).border({width:1,color:$r(app.color.divider)}).layoutWeight(1).margin({right:8}).onClick((){this.currentStep--;})}// 下一步/完成按钮Button(this.currentStepthis.steps.length-1?完成:下一步).height(44).backgroundColor($r(app.color.primary)).borderRadius(22).fontColor(Color.White).layoutWeight(1).margin({left:this.currentStep0?8:0}).onClick((){if(this.currentStepthis.steps.length-1){this.currentStep;// 继续下一步}else{router.back();// 完成返回}})}.width(90%).margin({bottom:24})交互逻辑位置上一步按钮下一步按钮第1步不显示文字下一步第2~6步显示文字下一步第7步显示文字完成点击返回margin.left的动态调整确保只有一个按钮时居中两个按钮时有间距。二、自定义主题 — CustomThemePage主题系统让用户自定义游戏的外观包括颜色方案和暗夜模式。2.1 主题数据结构interfaceThemeOption{id:number;// 主题IDname:string;// 主题名称primaryColor:string;// 主题主色bgColor:string;// 背景色cardColor:string;// 卡片色isDark:boolean;// 是否为暗夜模式preview:string;// 预览表情}2.2 8 套主题方案ID名称主色背景色特点0默认蓝 #FF5C6BC0#FFF5F5F5经典靛蓝1森林绿 #FF388E3C#FFF1F8E9清新自然2日落橙 #FFE64A19#FFFBE9E7温暖活力3星空紫 #FF7B1FA2#FFF3E5F5神秘深邃4深海蓝 #FF01579B#FFE1F5FE沉稳宁静5樱花粉 #FFC2185B#FFFCE4EC甜美柔和6暗夜模式 #FFBB86FC#FF121212护眼节能7极简灰 ⬜#FF424242#FFFAFAFA简约商务2.3 预览区域选中主题后预览区域实时展示效果Column(){Text(预览).fontSize($r(app.float.small_font_size))Column(){// 预览数独标题Text(this.themes[this.selectedTheme].preview).fontSize(48)Text(数独).fontSize(24).fontWeight(FontWeight.Bold).fontColor(this.themes[this.selectedTheme].primaryColor)Text(经典数独 挑战大脑).fontSize($r(app.float.small_font_size)).fontColor(this.themes[this.selectedTheme].isDark?#FFAAAAAA:#FF999999)// 预览按钮Row(){Button(简单).backgroundColor(this.themes[this.selectedTheme].primaryColor).fontColor(Color.White)Button(中等).backgroundColor(this.themes[this.selectedTheme].isDark?#FF333333:#FFF5F5F5).fontColor(this.themes[this.selectedTheme].primaryColor).border({width:1,color:this.themes[this.selectedTheme].primaryColor})}// 模拟小棋盘 (3×3 Grid)Grid(){ForEach([1,2,3,4,5,6,7,8,9],(idx:number){GridItem(){Text(idx5?idx.toString():).fontColor(idx3?this.themes[this.selectedTheme].primaryColor:#FF333333)}.aspectRatio(1).backgroundColor(idx5?#FFE8EAF6:Color.Transparent).border({width:0.5,color:#FFE0E0E0})})}.columnsTemplate(1fr 1fr 1fr).rowsTemplate(1fr 1fr 1fr).width(150).height(150).border({width:2,color:#FF333333})}.backgroundColor(this.themes[this.selectedTheme].cardColor).borderRadius($r(app.float.card_corner_radius))}.backgroundColor(this.themes[this.selectedTheme].isDark?#FF1E1E1E:$r(app.color.card_bg))预览区域动态变化的内容标题颜色 → 主题主色暗夜模式文字颜色 → 浅灰色实心按钮颜色 → 主题主色描边按钮颜色 → 主题主色模拟棋盘数字颜色 → 主题主色预览卡片背景色 → 主题卡片色整个预览容器背景 → 暗夜模式时为深色2.4 主题选择网格使用Grid组件以 4 列 2 行的网格展示所有主题StateselectedTheme:number0;Grid(){ForEach(this.themes,(theme:ThemeOption){GridItem(){Column(){// 颜色圆形Circle().width(40).height(40).fill(theme.primaryColor)Text(theme.name).fontSize($r(app.float.small_font_size)).fontColor($r(app.color.text_primary)).margin({top:6})// 选中标记if(this.selectedThemetheme.id){Text(✓).fontSize(14).fontColor($r(app.color.primary)).fontWeight(FontWeight.Bold)}}.padding(12).alignItems(HorizontalAlign.Center).backgroundColor(this.selectedThemetheme.id?#FFE8EAF6:$r(app.color.card_bg)).borderRadius($r(app.float.card_corner_radius)).border({width:this.selectedThemetheme.id?2:0,color:theme.primaryColor})}.padding(6).onClick((){this.selectedThemetheme.id;})},(theme:ThemeOption)theme.id.toString())}.columnsTemplate(1fr 1fr 1fr 1fr).rowsTemplate(1fr 1fr 1fr).width(95%).layoutWeight(1)选中的视觉反馈三重提示背景变成浅蓝色 (#FFE8EAF6)边框出现颜色为对应主题色2px对勾 ✓ 标记出现2.5 Grid 组件详解Grid是 ArkTS 提供的网格布局组件与前端 CSS Grid 类似Grid(){// GridItem 作为子元素GridItem(){/* 内容 */}}.columnsTemplate(1fr 1fr 1fr 1fr)// 4列每列等宽.rowsTemplate(1fr 1fr)// 2行每行等高fr单位表示份数1fr 1fr 1fr 1fr表示 4 列均分宽度。如果希望第一列更宽可以写2fr 1fr 1fr。Grid 与 ForEach 的区别GridGridItem自动排列适合固定模板的网格布局ColumnRowForEach完全手动控制适合复杂布局主题选择器用 Grid 最合适因为它是规则的 4 列排列三、项目整体架构回顾至此我们完成了所有 8 个页面的开发。让我们俯瞰整个项目的架构3.1 页面关系图┌─────────────────┐ │ Index.ets │ ← 首页 │ (难度选择入口) │ └────────┬────────┘ │ ┌──────────────────┼──────────────────┐ ▼ ▼ ▼ ┌─────────────┐ ┌──────────────┐ ┌────────────────┐ │ GamePage.ets│ │Leaderboard │ │ StatsPage.ets │ │ (游戏核心) │ │ Page.ets │ │ (数据统计) │ └─────────────┘ │ (排行榜) │ └────────────────┘ └──────────────┘ ┌──────────────┐ ┌──────────────┐ ┌────────────────┐ │SettingsPage │ │Achievements │ │ TutorialPage │ │.ets (设置) │ │Page.ets(成就)│ │ .ets (教程) │ └──────────────┘ └──────────────┘ └────────────────┘ ┌─────────────────┐ │ CustomThemePage │ │ .ets (主题) │ └─────────────────┘3.2 模块职责划分层次职责文件Ability层生命周期管理、窗口创建EntryAbility.ets页面层UI 渲染、用户交互pages/*.ets路由层页面跳转、参数传递ohos.router资源层字符串/颜色/尺寸集中管理resources/构建层模块配置、编译选项build-profile.json5, module.json53.3 ArkTS 设计模式总结在 5 篇博文的开发中我们反复使用了以下模式1. State 数据驱动模式用户操作 → 修改State → 框架自动更新UI → 用户看到新界面适用所有交互页面游戏、设置、成就、主题2. 计算属性模式get filteredAchievements(): Type[] { // 依赖 State 变量自动计算 }适用列表筛选、数据统计3. 条件渲染模式if (condition) { // 只在条件满足时渲染 }适用已完成/未完成状态、导航按钮切换4. 卡片容器模式Column() .backgroundColor($r(app.color.card_bg)) .borderRadius($r(app.float.card_corner_radius)) .padding(16)适用内容分组、设置项、统计卡片5. 列表-详情模式List → ListItem → Column → Text/Image适用排行榜、成就列表四、Route 路由最佳实践4.1 统一 RouteOpt 接口所有页面使用同一套路由接口定义interfaceRouteOpt{url:string;params?:Object;}这是为了满足 ArkTS 严格模式的对象字面量类型要求。4.2 页面入口配置所有页面必须在main_pages.json中注册{src:[pages/Index,pages/GamePage,pages/LeaderboardPage,pages/StatsPage,pages/SettingsPage,pages/TutorialPage,pages/AchievementsPage,pages/CustomThemePage]}常见错误页面未注册时跳转会报错Page not found。4.3 参数传递的类型安全// 发送方router.pushUrl({url:pages/GamePage,params:{difficulty:easy}});// 接收方aboutToAppear():void{constparamsrouter.getParams()asRecordstring,Object;if(paramsparams[difficulty]){this.difficultyparams[difficulty]asstring;}}使用Recordstring, Object进行类型断言接收参数然后用as string进行二次类型转换。五、资源管理的最佳实践5.1 $r 引用规范整个项目中的颜色、字号、字符串全部通过$r引用没有一个硬编码值引用方式示例对应文件$r(app.string.xxx)按钮文字、标题string.json$r(app.color.xxx)背景色、文字色color.json$r(app.float.xxx)字号、间距、圆角float.json5.2 深色模式资源项目还预备了深色模式的资源文件entry/src/main/resources/ ├── base/ # 默认资源 │ └── element/ │ ├── color.json │ ├── float.json │ └── string.json └── dark/ # 深色模式资源 └── element/ └── color.json # 深色颜色覆盖当系统切换到深色模式时框架自动加载dark/element/color.json覆盖同名的颜色值。六、从开发到上线的优化方向6.1 短期优化1-2 天数据持久化使用PersistentStorage保存游戏进度、成就解锁状态、设置偏好真正的数独生成器用回溯算法替代预设棋盘实现真正的无限随机题目动画效果为成就解锁、游戏完成添加 transition 动画6.2 中期优化1 周网络对战接入鸿蒙网络框架实现好友对战云同步使用华为账号服务Account Kit同步进度多端适配适配平板折叠屏、手表等更多设备类型deviceTypes本地化完善多语言资源如英文en.json6.3 长期优化性能优化分析 hvigor 构建产物优化包体积无障碍添加 contentDescription 支持屏幕阅读统计分析接入华为分析服务Analytics Kit七、开发心得总结通过这 5 篇博文的实战开发我们完整地体验了鸿蒙原生应用从零到一的过程7.1 学到了什么知识点对应博文Stage 模型 项目结构第一篇ArkTS 组件化开发 (Entry, Component)第一篇路由导航 (ohos.router)第一篇资源文件管理 ($r)第一篇数独生成算法 棋盘渲染第二篇交互逻辑选中/填数/笔记/提示第二篇计时器生命周期第二篇Toggle 开关 Scroll 滚第三篇数据统计展示第三篇成就系统设计 进度追踪第四篇排行榜 列表渲染优化第四篇条件渲染 (if)第四篇交互式教程 (步进器)第五篇主题系统 (Grid 网格)第五篇7.2 核心技术栈语言ArkTS鸿蒙版 TypeScript框架Stage 模型UI 框架ArkUI声明式 UI构建工具hvigorIDEDevEco StudioAPI 版本API 237.3 一句话总结鸿蒙原生应用开发吸取了现代移动开发的最佳实践——声明式 UI、数据驱动、组件化、资源分离——同时保持了与 Android/iOS 不同的设计哲学更强调跨设备协同和系统级服务集成。写在最后感谢你跟随这 5 篇博文完成了整个数独游戏的开发之旅。从第一个页面的搭建到复杂的游戏逻辑再到辅助功能和个性化设置每一步都是鸿蒙原生开发技能的积累。当然这个项目还有很多可以完善的地方——数据持久化、网络对战、AI 解题等。但这些都留给你去探索和实现。如果你在开发过程中遇到问题欢迎留言交流。Happy coding