大家好欢迎来到鸿蒙音乐播放器实战系列的第一篇。这个系列我们会从零开始手把手带大家做一个连贯完整、可迭代商用的鸿蒙原生音乐播放器全程不做单页演示、不写废弃占位代码。从项目骨架、页面路由、导航架构到UI布局、播放能力、后台保活循序渐进完成完整项目开发。作为开篇第一篇我们核心完成项目底层骨架搭建统一页面创建规范、配置全局路由体系、搭建全局导航栈、实现商用全屏广告启动页同时完整搭建主页基础骨架保证APP启动、跳转全流程闭环可运行为后续所有功能迭代打底。本篇学习目标理解鸿蒙Stage模型的项目结构理清页面、路由、入口函数的对应关系掌握router_map.json路由表配置规则独立完成多页面路由注册理解 Navigation NavPathStack 导航原理搭建全局统一导航架构分清pushPathByName与replacePathByName的业务使用场景实现全屏沉浸式广告启动页3秒自动跳转手动跳过掌握鸿蒙安全区适配、无报错页面创建、路由注册全套规范解决导入报错、路由找不到、页面空白、返回退回广告页等常见问题一、先理清我们的项目整体结构连贯开发本项目为完整连贯工程所有页面逐层迭代开发无临时占位、无废弃代码核心四大页面结构固定Index 入口页APP根容器仅承载全局导航栈不展示业务UIStart 广告启动页APP首个展示页负责启动过渡、自动/手动跳转主页Layout 布局主页APP核心主页后续迭代底部Tab、多页面切换、首页UIPlay 播放页后续开发歌曲播放、动画、播控、播放列表页面全局统一采用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/base2. 无profile文件夹则右键 base → New → Directory命名为profile3. 选中 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文件命名Start2. 核心规则所有路由页面必须被 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文件命名Layout2. 带详细注释零报错完整骨架代码// 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.textColorIndexindex?#E85a88:#63AAAA) Text(item.text).font({ size: 14 }).fontColor(this.textColorIndexindex?#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界面。