鸿蒙音乐播放器实战01|从零搭建项目骨架:导航架构与广告启动页完整实现
大家好,欢迎来到鸿蒙音乐播放器实战系列的第一篇。
这个系列我们会从零开始,手把手带大家做一个连贯完整、可迭代商用的鸿蒙原生音乐播放器,全程不做单页演示、不写废弃占位代码。从项目骨架、页面路由、导航架构,到UI布局、播放能力、后台保活,循序渐进完成完整项目开发。
作为开篇第一篇,我们核心完成项目底层骨架搭建:统一页面创建规范、配置全局路由体系、搭建全局导航栈、实现商用全屏广告启动页,同时完整搭建主页基础骨架,保证APP启动、跳转全流程闭环可运行,为后续所有功能迭代打底。
本篇学习目标
-
理解鸿蒙Stage模型的项目结构,理清页面、路由、入口函数的对应关系
-
掌握
router_map.json路由表配置规则,独立完成多页面路由注册 -
理解 Navigation + NavPathStack 导航原理,搭建全局统一导航架构
-
分清
pushPathByName与replacePathByName的业务使用场景 -
实现全屏沉浸式广告启动页(3秒自动跳转+手动跳过)
-
掌握鸿蒙安全区适配、无报错页面创建、路由注册全套规范
-
解决导入报错、路由找不到、页面空白、返回退回广告页等常见问题
一、先理清:我们的项目整体结构(连贯开发)
本项目为完整连贯工程,所有页面逐层迭代开发,无临时占位、无废弃代码,核心四大页面结构固定:
-
Index 入口页:APP根容器,仅承载全局导航栈,不展示业务UI
-
Start 广告启动页:APP首个展示页,负责启动过渡、自动/手动跳转主页
-
Layout 布局主页:APP核心主页,后续迭代底部Tab、多页面切换、首页UI
-
Play 播放页:后续开发,歌曲播放、动画、播控、播放列表页面
全局统一采用Navigation + NavPathStack 官方导航方案,所有页面跳转、栈管理统一受控,保证项目架构统一规范。
二、第一步:路由配置,给每个页面办「身份证」
鸿蒙Stage模型硬性规则:所有页面必须提前在路由表注册,否则无法跳转、系统识别不到页面。
1. 声明路由表文件
打开项目配置文件src/main/module.json5,在 module 节点下新增路由表声明,告诉系统路由配置文件位置:
{
"module": {
// ... 原有默认配置
"routerMap": "$profile:router_map",
// ... 原有默认配置
.
.
.
作用:绑定 resources/base/profile/router_map.json 为项目全局唯一路由表。
2. 手把手创建路由表文件(零报错步骤)
严格按步骤手动创建,杜绝路径、文件不存在报错:
1. 定位项目目录:src/main/resources/base
2. 无 profile 文件夹则:右键 base → New → Directory,命名为 profile
3. 选中 profile 文件夹:右键 → New → File,全名创建 router_map.json
路由表核心字段说明:
-
name:路由别名(代码跳转调用,大小写严格敏感) -
pageSourceFile:页面ETS文件完整绝对路径 -
buildFunction:页面路由入口Builder函数名(必须和页面导出函数一致)
粘贴完整路由配置,一次性注册本篇所有页面,保证链路闭环:
{
"routerMap": [
{ "name": "Start",
"pageSourceFile": "src/main/ets/pages/Start.ets",
"buildFunction": "StartBuilder" },
{ "name": "Layout",
"pageSourceFile": "src/main/ets/pages/Layout.ets",
"buildFunction": "LayoutBuilder"
}
]
}
3. 全局页面创建统一强制规范(全程通用)
本项目所有页面统一创建规则,全程不变:
✅ 唯一正确方式:右键 pages 目录 → New → ArkTS File,新建空白ETS文件
❌ 绝对禁止:使用右键自带的 Page、列表页、标签页等模板
(也可根据个人习惯)
删除线模板报错解释(新手必看):
模板带删除线 = 废弃不兼容,这类模板是老旧FA模型专属,我们使用的是最新Stage模型,强行使用会出现API报错、路由失效、代码冗余冲突。
所有页面手写空白文件,架构干净、完全适配我们的自定义导航路由体系,无兼容问题。
4. 路由页面 Builder 函数强制规范
所有路由注册页面,必须导出全局 @Builder 入口函数,否则系统找不到页面,路由直接报错。
函数作用:作为路由跳转的唯一入口,供系统反射调用,绑定页面组件。
固定标准写法(所有页面通用):
// @Builder:构建器装饰器,用于路由注册页面入口
// export:必须导出,否则外部路由无法识别
@Builder
export function 页面名Builder(name: string, param: string) {
// 固定写法:实例化当前页面,接收路由传参
页面名({ name: name, value: param });
}
新建页面固定四步流程:新建空白ArkTS文件 → 编写页面组件 → 编写导出Builder入口函数 → 路由表注册。
重要说明:@Builder、@Component、@Entry、Stack、Image、Button、Alignment、Color 均为鸿蒙全局内置API,无需手动导入,强行导入会直接编译报错。
三、第二步:搭建全局导航骨架(Index入口页)
Index页面是整个APP的唯一入口根容器,核心作用是通过AppStorageV2全局挂载导航栈,实现全项目页面导航栈共享,彻底解决多页面栈冲突、跳转失效问题,统一托管所有页面路由跳转。
Index.ets 创建与完整代码
文件位置:src/main/ets/pages/Index.ets
创建方式:默认自带,缺失则右键pages新建ArkTS文件,命名Index
清空默认代码,粘贴以下带完整注释、可直接运行的标准代码:
// AppStorageV2:全局状态持久化工具,实现跨页面共享导航栈
import { AppStorageV2 } from '@kit.ArkUI';
/**
* 首页路由入口构建函数
* 适配全局路由体系,作为Index首页的路由注册入口
*/
@Builder
export function IndexBuilder() {
Index();
}
// APP全局唯一入口页面
@Entry
@Component
struct Index {
/**
* 全局共享导航栈
* 通过AppStorageV2全局挂载,实现项目所有页面跨页面共用同一个导航栈
* 避免多栈冲突、页面跳转失效、页面栈错乱问题
*/
pageStack: NavPathStack = AppStorageV2.connect(NavPathStack, 'navStack', () => new NavPathStack())!;
build() {
// 全局导航根容器,托管项目所有路由页面
Navigation(this.pageStack) {
// 根页面无需自定义UI,空列布局占位即可
Column() {}
}
// 铺满全屏
.width('100%')
.height('100%')
// 设置根容器透明,不遮挡下级页面UI
.backgroundColor(Color.Transparent)
// 全局扩展安全区,适配沉浸式全屏效果
.expandSafeArea([SafeAreaType.SYSTEM], [SafeAreaEdge.TOP, SafeAreaEdge.BOTTOM])
// 隐藏系统原生顶部导航栏,全程自定义页面样式
.hideNavBar(true)
// 根页面加载完成触发
.onAppear(() => {
// 延迟500ms执行跳转,规避Navigation初始化未完成导致的跳转失效问题
setTimeout(() => {
// 控制台打印日志,方便调试查看跳转状态
console.info('开始跳转到广告页');
// 根据路由名跳转至广告启动页
this.pageStack.pushPathByName("Start", null, false);
}, 500);
})
}
}
四、第三步:广告启动页完整开发(Start.ets)
实现效果:全屏沉浸式广告背景 + 右上角跳过按钮 + 3秒自动跳转主页 + 手动点击立即跳转,符合主流APP启动逻辑。
1. 新建页面
文件位置:src/main/ets/pages/Start.ets
创建方式:右键pages新建空白ArkTS文件,命名Start
2. 核心规则
所有路由页面必须被 NavDestination 包裹,否则无法获取导航栈上下文,页面空白、跳转失效。
3. 最终带详细注释完整代码
//第一步 建立自定义入口函数
@Builder
export function StartBuilder(name: string, param: string) {
Start({ name: name, value: param });
}
// 广告启动页组件
@Component
export struct Start {
// 定义导航栈实例,用于当前页面跳转
navPathStack: NavPathStack = new NavPathStack();
/**
* 页面生命周期:页面即将显示时触发
* 作用:开启3秒自动跳转定时器
*/
aboutToAppear(): void {
setTimeout(()=>{
this.navPathStack.replacePathByName("Layout",null,false)
},3000)
}
// 路由接收参数
name: string = '';
@State value: string = '';
build() {
// 路由页面专属容器,必须包裹所有UI,用于承接导航上下文
NavDestination() {
// 堆叠布局:实现底层背景图 + 上层按钮层级效果
Stack({alignContent:Alignment.TopEnd}){
// 全屏广告背景图
Image($rawfile("ad.png"))
.width("100%").height("100%")
// 安全区扩展:图片延伸至状态栏、底部手势栏,消除上下白边
.expandSafeArea([SafeAreaType.SYSTEM],[SafeAreaEdge.TOP,SafeAreaEdge.BOTTOM])
// 右上角跳过按钮
Button("跳过").backgroundColor(Color.Gray)
.margin(15)
.onClick(()=>{
//this.navPathStack.replacePathByName("Layout",null,false)
this.navPathStack.pushPathByName("Layout",null,false)
})
}
}
//.title("广告页")
// ctx.pathStack 导航控制器放入this.navPathStack
.onReady((ctx: NavDestinationContext) => {
this.navPathStack = ctx.pathStack;
})
}
}
4. 跳转方式核心避坑详解
-
pushPathByName(弃用):入栈跳转,保留广告页栈,用户返回键会退回广告页,不符合商用APP逻辑
-
replacePathByName(全程使用):替换当前页面,销毁广告页栈,进入主页后返回直接退出APP,符合主流APP启动逻辑
五、第四步:搭建主页基础骨架(Layout.ets)
为保证项目完整连贯可运行,本篇直接落地Layout主页基础架构,绝非临时占位。下一篇博客将在此骨架上迭代底部Tab、首页轮播、推荐卡片等完整UI,全程复用本篇代码,无重构、无废弃。
1. 新建页面
文件位置:src/main/ets/pages/Layout.ets
创建方式:右键pages新建空白ArkTS文件,命名Layout
2. 带详细注释零报错完整骨架代码
// import { Recommend1 } from '../component/Recommend1'
// import { FindPage } from '../component/FindPage';
// 定义一个类
interface TabClass {
text : string,
icon : ResourceStr
}
@Builder
export function LayoutBuilder() {
Layout(); // 对应你的 struct 名字
}
@Component
export struct Layout {
navPathStack: NavPathStack = new NavPathStack()
tabData: TabClass[] =[
{text:'推荐', icon: $rawfile('ic_recommend.svg')},
{text:'发现',icon:$rawfile('ic_find.svg')},
{text:'动态',icon:$rawfile('ic_moment.svg')},
{text: '我的', icon: $rawfile('ic_mine.svg')}
]
@State textColorIndex : number = 0;
@Builder tarBuilder(item: TabClass, index: number) {
Column({ space: 5 }) {
Image(item.icon).width(24).fillColor(this.textColorIndex===index?'#E85a88':'#63AAAA')
Text(item.text).font({ size: 14 }).fontColor(this.textColorIndex===index?'#E85a88':'#63AAAA')
}
}
build() {
NavDestination() {
// barPosition: BarPosition.End 菜单至于底部。
Tabs({ barPosition: BarPosition.End }) {
ForEach(this.tabData, (item: TabClass, index) => {
TabContent() {
// 推荐页直接用你写好的 Recommend1 组件
if (index === 0) {
// Recommend1() // 推荐
}
else if (index === 1) {
// FindPage() // 发现
}
else {
// 其他页面先放个占位文本
Column() {
Text(`${item.text}页面`).fontSize(20)
}.width('100%').height('100%').justifyContent(FlexAlign.Center)
}
}.tabBar(this.tarBuilder(item, index))
.backgroundColor('#131215')
})
}.backgroundColor('#3B3F42')
.onChange((index) => {
this.textColorIndex = index
})
}.onReady((ctx: NavDestinationContext) => {
// 获取共享的导航栈
this.navPathStack = ctx.pathStack;
})
}
}
至此,项目完整启动链路彻底跑通:APP启动 → Index加载导航栈 → 自动进入Start广告页 → 3秒/手动跳转Layout主页,无报错、无空白、无异常回退。
效果展示:


六、新手专属:全套报错解决方案
-
报错:buildFunction 不存在 排查:页面是否导出对应Builder函数、函数名和路由表大小写完全一致、文件路径无错误。
-
问题:广告页上下有白边,无法全屏 排查:Image宽高100%、是否添加
expandSafeArea安全区扩展代码。 -
问题:跳转后页面空白 排查:路由页面是否包裹
NavDestination、Builder函数是否添加 export 导出。 -
问题:返回键退回广告页 排查:所有启动页跳转统一使用
replacePathByName,禁止使用push。 -
问题:导入代码编译报错 解决方案:组件、装饰器(Stack/Image/Button/@Builder/@Component等)均为全局内置,无需导入,仅保留导航、安全区专属API导入即可。
本篇总结
本篇完成了完整可运行的项目底层架构:统一页面创建规范、配置全局路由表、搭建全局导航栈、实现商用级全屏广告启动页、落地主页基础骨架,全程无冗余代码、无报错、无临时占位,保证项目连贯可迭代。
本篇所有代码、规范、架构将贯穿整个系列,后续所有功能均基于本篇骨架迭代,无需重构。
下篇预告
下一篇将基于当前Layout主页骨架,开发底部Tab选项卡、图标文字高亮、多页面切换、首页搜索栏、轮播图、音乐推荐卡片等完整静态UI界面。
更多推荐


所有评论(0)