鸿蒙原生应用实战(五):教程、主题与项目总结 — 从开发到上线的完整回顾
鸿蒙原生应用实战(五):教程、主题与项目总结 — 从开发到上线的完整回顾
前言
这是本系列的最后一篇。我们将完成剩余两个页面——教程页面(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:number=0;// 当前步骤索引(0开始)privatesteps:StepItem[]=[/* 7个步骤 */];currentStep是唯一的@State变量。每次用户点击"上一步/下一步"时修改它,触发整个页面重新渲染。
1.5 进度条实现
教程顶部有一个 7 段进度条,直观显示当前位置:
Row(){ForEach(this.steps,(step:StepItem,index:number)=>{Column().layoutWeight(1).height(4).backgroundColor(index<=this.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.currentStep>0){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.currentStep===this.steps.length-1?'完成':'下一步').height(44).backgroundColor($r('app.color.primary')).borderRadius(22).fontColor(Color.White).layoutWeight(1).margin({left:this.currentStep>0?8:0}).onClick(()=>{if(this.currentStep<this.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(idx<=5?idx.toString():'').fontColor(idx<=3?this.themes[this.selectedTheme].primaryColor:'#FF333333')}.aspectRatio(1).backgroundColor(idx===5?'#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:number=0;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.selectedTheme===theme.id){Text('✓').fontSize(14).fontColor($r('app.color.primary')).fontWeight(FontWeight.Bold)}}.padding(12).alignItems(HorizontalAlign.Center).backgroundColor(this.selectedTheme===theme.id?'#FFE8EAF6':$r('app.color.card_bg')).borderRadius($r('app.float.card_corner_radius')).border({width:this.selectedTheme===theme.id?2:0,color:theme.primaryColor})}.padding(6).onClick(()=>{this.selectedTheme=theme.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 的区别:
Grid+GridItem:自动排列,适合固定模板的网格布局Column+Row+ForEach:完全手动控制,适合复杂布局- 主题选择器用 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.json5 |
3.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{constparams=router.getParams()asRecord<string,Object>;if(params&¶ms['difficulty']){this.difficulty=params['difficulty']asstring;}}使用Record<string, Object>进行类型断言接收参数,然后用as string进行二次类型转换。
五、资源管理的最佳实践
5.1 $r 引用规范
整个项目中的颜色、字号、字符串全部通过$r引用,没有一个硬编码值:
| 引用方式 | 示例 | 对应文件 |
|---|---|---|
$r('app.string.xxx') | 按钮文字、标题 | string.json |
$r('app.color.xxx') | 背景色、文字色 | color.json |
$r('app.float.xxx') | 字号、间距、圆角 | float.json |
5.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.json)
6.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)
- 构建工具:hvigor
- IDE:DevEco Studio
- API 版本:API 23
7.3 一句话总结
鸿蒙原生应用开发吸取了现代移动开发的最佳实践——声明式 UI、数据驱动、组件化、资源分离——同时保持了与 Android/iOS 不同的设计哲学:更强调跨设备协同和系统级服务集成。
写在最后
感谢你跟随这 5 篇博文完成了整个数独游戏的开发之旅。从第一个页面的搭建,到复杂的游戏逻辑,再到辅助功能和个性化设置,每一步都是鸿蒙原生开发技能的积累。
当然,这个项目还有很多可以完善的地方——数据持久化、网络对战、AI 解题等。但这些都留给你去探索和实现。
如果你在开发过程中遇到问题,欢迎留言交流。Happy coding!🚀
