当前位置: 首页 > backend >正文

游戏详情制作(Navigation组件)

1.1 需求

使用Navigation实现游戏主详情视图,从瀑布流容器中的游戏项(游戏中心首页-游戏瀑布流列表)点击游戏后进入游戏详情页,从游戏详情页可以返回游戏列表主页。

1.2 界面原型

从瀑布流组件进入:

在这里插入图片描述

游戏详情:

在这里插入图片描述

2 预备知识

2.1 Navigation组件

2.1.1 Navigation路由导航组件

  1. 实现页面间及组件内部的页面跳转,也可以实现跨包跳转
  2. 支持传递跳转参数
  3. 包含导航页和子页
    1. 导航页根容器是Navigation
    2. 子页根容器是NavDestination,用于显示Navigation的内容区
    3. 导航页不存在与页面栈中,与子页,甚至是子页之间通过路由操作进行切换
  4. Navigation:导航页包含标题栏(包含菜单栏)、内容区和工具栏,hideToolBar(value: boolean)属性用于显隐工具栏
  5. NavDestination子页包含标题栏,标题栏包含主副标题和返回键,如未设置主副标题并没有返回键时则不显示标题栏,hideTitleBar属性用于显隐标题栏

2.1.2 子页面

NavDestination是Navigation子页面的根容器

页面显示类型

  1. 标准类型
    1. NavDestination组件默认为标准类型
    2. mode属性为NavDestinationMode.STANDARD
  2. 弹窗类型
    1. NavDestination设置mode为NavDestinationMode.DIALOG弹窗类型
    2. 整个NavDestination默认透明显示

2.1.3 NavPathStack路由操作

  • NavPathStack路由栈:
  1. Navigation路由相关的操作都是基于页面栈NavPathStack提供的方法进行
  2. 每个Navigation都需要创建并传入一个NavPathStack对象
  3. 页面管理涉及页面跳转、页面返回、页面替换、页面删除、参数获取、路由拦截等功能
  • 页面跳转:

NavPathStack通过Push相关的接口去实现页面跳转的功能

  1. 普通跳转,通过页面的name去跳转,并可以携带param
this.pageStack.pushPath({ name: "PageOne", param: "PageOne Param" })
this.pageStack.pushPathByName("PageOne", "PageOne Param")
  1. 带返回回调的跳转,跳转时添加onPop回调,能在页面出栈时获取返回信息,并进行处理
this.pageStack.pushPathByName('PageOne', "PageOne Param", (popInfo) => {console.log('Pop page name is: ' + popInfo.info.name + ', result: ' + JSON.stringify(popInfo.result))
});
  • 页面返回:

NavPathStack通过Pop相关接口去实现页面返回功能

// 返回到上一页
this.pageStack.pop()
// 返回到上一个PageOne页面
this.pageStack.popToName("PageOne")
// 返回到索引为1的页面
this.pageStack.popToIndex(1)
// 返回到根首页(清除栈中所有页面)
this.pageStack.clear()
  • 页面替换:

NavPathStack通过Replace相关接口去实现页面替换功能

// 将栈顶页面替换为PageOne
this.pageStack.replacePath({ name: "PageOne", param: "PageOne Param" })
this.pageStack.replacePathByName("PageOne", "PageOne Param")
  • 页面删除:

NavPathStack通过Remove相关接口去实现删除页面栈中特定页面的功能

// 删除栈中name为PageOne的所有页面
this.pageStack.removeByName("PageOne")
// 删除指定索引的页面
this.pageStack.removeByIndexes([1,3,5])
// 删除指定id的页面
this.pageStack.removeByNavDestinationId("1");
  • 移动页面:

NavPathStack通过Move相关接口去实现移动页面栈中特定页面到栈顶的功能

// 移动栈中name为PageOne的页面到栈顶
this.pageStack.moveToTop("PageOne");
// 移动栈中索引为1的页面到栈顶
this.pageStack.moveIndexToTop(1);
  • 参数获取

NavPathStack通过Get相关接口去获取页面的一些参数

// 获取栈中所有页面name集合
this.pageStack.getAllPathName()
// 获取索引为1的页面参数
this.pageStack.getParamByIndex(1)
// 获取PageOne页面的参数
this.pageStack.getParamByName("PageOne")
// 获取PageOne页面的索引集合
this.pageStack.getIndexByName("PageOne")

2.2 @Provide 和@Consume装饰器

2.2.1 概述

  1. @Provide和@Consume,用于与后代组件的双向数据同步,状态数据实现跨层级传递。(不限于父子,可以是孙辈,穿越能力)

  2. 通过相同的变量名或者相同的变量别名绑定(建议类型相同,否则会发生类型隐式转换,从而导致应用行为异常)。

  3. @Provide装饰的变量,在祖先组件中【提供】信息,@Consume在后代组件中【消费】信息

  4. 跨组件双向同步

  5. @State和@Link组合仅限于父子组件间双向数据同步

  6. 框架会使用map的形式处理@Provide和@Consume变量,通过map形式传递给当前@Provide所属的所有子组件,子组件在使用@Consume变量时,会从map中查找变量名和别名对应的@Provide变量,并向@Provide注册,所有别名相当于key,必须为string类型

  7. 更多指导:

    https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-provide-and-consume

2.2.2 @Provide装饰器

  1. 可以使用参数指定别名,指定别名则通过别名绑定变量,未指定别名则通过变量名绑定变量
  2. 支持类型包括:string、number、boolean、Date、enum、Object、class、Map、Set
  3. 必须赋初值
  4. 私有属性,仅可在组件内访问

2.2.3 @Consume装饰器

  1. 可以使用参数指定别名,指定别名则通过别名匹配变量,未指定别名则通过变量名匹配变量
  2. 类型需和@Provide保持一致
  3. 不可赋初值
  4. 私有属性,仅可在组件内访问

2.3 Flex布局

Flex:弹性布局,以弹性方式布局子组件的容器组件

  1. direction: 主轴方向,默认:FlexDirection.Row
    1. Row、RowReverse(从右到左)
    2. Column、ColumnReverse(从下向上)
  2. wrap:FlexWrap换行
    1. NoWrap:默认不换行,超过尺寸会压缩
    2. Wrap:换行
    3. WrapReverse:反向换行
  3. justifyContent、alignItems同线性布局
  4. alignContent:FlexAlign:多行内容时交叉轴内容对齐

内容参考:

https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-flex

2.4 自定义组件生命周期&页面生命周期

2.4.1 自定义组件VS页面

  1. 自定义组件:由@Component装饰的UI单元
  2. 页面:应用的UI页面,由一个或多个自定义组件构成,@Entry装饰的自定义组件是页面的入口组件,即页面根节点。

2.4.3 自定义组件生命周期

提供以下接口:

  • aboutToAppear:组件即将出现时回调,具体时机:创建自定义组件的新实例后,在执行build函数之前执行
  • onDidBuild: 组件build函数执行完成之后进行的回调。可用于埋点数据上报等不影响实际UI的功能。
  • aboutToDisappear: 自定义组件析构销毁之前执行。

2.4.4 页面生命周期

被@Entry装饰的组件生命周期,提供以下生命周期接口:

  • onPageShow:页面每次显示时触发,包括路由过程、应用进入前台等场景
  • onPageHide:页面每次隐藏时触发,包括路由过程、应用进入后台等场景
  • onBackPress:当用户点击返回按钮时触发

页面生命周期流程:

在这里插入图片描述

3 改造导航页

需将GameCenterHome组件改造为导航页,因为要求点击瀑布流组件中的游戏图片,导航到游戏详情页面,改造过程如下:

  1. 声明路由栈
 @Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack();

需要和子页进行路由同步,因此需要使用@Provide装饰器,并通过参数传递别名子页@Consume在使用时,需要与此处别名保持一致

  1. 将Navigation作为根组件,并传递路由栈
@Component
export default struct GameCenterHome{
...@Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack();build() {Navigation(this.pageInfos){Scroll(){...}.navDestination(this.pagesMap)}@BuilderpagesMap(name: string, param:number){if(name === 'GameDetail'){GameDetailComponent()//游戏详情页组件}}...

Note:

  1. Navigation的navDestination属性:
    navDestination(builder: (name: string, param: unknown) => void)
    创建NavDestination组件。使用builder函数,基于name和param构造NavDestination组件。
  2. @Builder pagesMap(name: string, param:number):
    使用条件渲染定义自定义组件作为Navigation的子页(该组件需要使用NavDestination作为根组件)
  1. 点击瀑布流游戏图片进行导航,并未导航子页传递参数:
     //瀑布流组件WaterFlow({footer: ():void =>this.itemLoadFoot(),scroller: this.scroller}){LazyForEach(this.datasource,(item: GameInfoBean, index)=>{FlowItem(){this.waterFlowItemCell(item)}.onClick(()=>{this.pageInfos.pushPathByName('GameDetail',item.id)})})}...

4 建立导航子页

在components下创建arkts文件,命名为GameDetailComponent,并编写如下代码:

@Component
export struct GameDetailComponent {@Consume('pageInfos') pageInfos: NavPathStack;build() {NavDestination(){Column(){Text('detail:'+ this.pageInfos.getParamByName('GameDetail'))}}.title('游戏详情').backgroundColor('#f1f3f5')}}

Note:

  1. 需要接收导航页的路由栈,使用@Consume双向同步,按别名匹配
  2. 使用NavDestination作为根容器,并设置标题为:游戏详情
  3. 通过路由栈的getParamByName(‘GameDetail’)获取导航页传递来的参数。

预览效果

从MainPage进入预览:

在这里插入图片描述

跳转到导航子页:

在这里插入图片描述

5 布局游戏详情

5.1 界面原型

在这里插入图片描述

5.2 准备游戏详情数据

  1. 首先封装游戏详情信息,在model下新建arkts文件:GameDetailBean,定义为类实现自GameInfoBean接口,扩展如下属性:

游戏icon,游戏关注数,热度,评价数,帖子数,游戏详情图片

export default class GameDetailBean implements  GameInfoBean{id: number;imageUrl: string | Resource;name: string;score: number;type: string;desc: string;//新增属性icon: string | Resource;//logoflowsCount:number;//关注数hotCount: number;//热度commentCount: number;//评论数topicCount: number;//帖子数imageUrlsArray: Array<Resource>constructor() {this.id = 10;this.imageUrl = $rawfile('gamewaterflow/game111.png');this.name = '火柴人战争2';this.score = 9;this.type = '卡通 战争 解密';this.desc = '火柴人战争游戏是一个家喻户晓的游戏,在很久很久以前,火柴人和人类发生了一场战争。';this.icon = $rawfile('gamecenter/gamelogo1.png');this.flowsCount = 1000;this.hotCount = 666;this.commentCount = 88;this.topicCount = 1890;this.imageUrlsArray = [$r('app.media.gameicon4'),$r('app.media.gameicon5'),$r('app.media.gameicon6')];}}
  1. 根据游戏ID返回游戏详情数据,在GameHomeViewModel添加函数getGameDetail:
  getGameDetail(id:number):GameDetailBean {let gameDetailBean = new GameDetailBean();return gameDetailBean;}
  1. 在游戏详情组件上获取游戏详情信息,在GameDetailComponent中编写代码,首先定义要展示的游戏id,游戏详情,并在组件要出现时获取游戏详情:
  private gameId:number = 0;@State gameDetailBean:GameDetailBean = new GameDetailBean();aboutToAppear(): void {this.gameId = this.pageInfos.getParamByName('GameDetail')[0] as number;console.info('gameId:'+this.gameId)this.gameDetailBean = GameHomeViewModel.getGameDetail(this.gameId)}

5.3 游戏logo部分

继续在GameDetailComponent中编码:

      Column(){//Text('detail:'+ this.pageInfos.getParamByName('GameDetail'))// 游戏logo部分Row(){Row({space:5}){Image(this.gameDetailBean.icon).width(64).height(64).borderRadius(12)Text(this.gameDetailBean.name).fontSize(22).fontWeight(FontWeight.Bold)}Column(){Text('评分:'+this.gameDetailBean.score.toFixed(1))Rating({ rating: 5*this.gameDetailBean.score/10, indicator: false }).width('80')}}.width('95%').justifyContent(FlexAlign.SpaceBetween)}

预览效果:

在这里插入图片描述

5.4 统计栏展示部分

使用Flex布局统计栏,调用@Builder函数

        //统计栏展示 flex布局this.counterBar(this.gameDetailBean)

@Builder函数封装:

  @Builder counterBar(gameItem:GameDetailBean){Flex({justifyContent:FlexAlign.SpaceAround}){Column({space:5}){Text(`${gameItem.flowsCount}`).counterBarTextStyle()Text('关注').counterBarTextStyle()}Divider().vertical(true).height(30)Column({space:5}){Text(`${gameItem.hotCount}`).counterBarTextStyle()Text('热度').counterBarTextStyle()}Divider().vertical(true).height(30)Column({space:5}){Text(`${gameItem.commentCount}`).counterBarTextStyle()Text('评价').counterBarTextStyle()}Divider().vertical(true).height(30)Column({space:5}){Text(`${gameItem.topicCount}`).counterBarTextStyle()Text('帖子').counterBarTextStyle()}}.width('95%').margin(10)}

文本样式:

@Extend(Text) function counterBarTextStyle(){.fontSize(12).opacity(0.6)
}

预览效果:

在这里插入图片描述

5.5 详情展示

使用Swiper组件展示:

        // 详情部分Text('详情').fontSize(18).fontWeight(FontWeight.Bold).width('95%').margin({top:20,bottom:10})Swiper(){ForEach(this.gameDetailBean.imageUrlsArray,(item:Resource)=>{Image(item).width('50%').height(120).borderRadius(12)},(item:Resource)=>JSON.stringify(item))}.width('95%').autoPlay(true).displayCount(2).itemSpace(10)

预览效果:

在这里插入图片描述

5.6 简介部分

展示游戏类型,描述和进入游戏按钮:

        // 简介部分Text('简介').fontSize(18).fontWeight(FontWeight.Bold).width('95%').margin({top:20,bottom:10})this.gameIntroduce(this.gameDetailBean)

在@Builder中封装展示内容,游戏类型需拆分成字符串数组使用foreach进行展示:

  @Builder gameIntroduce(gameDetail:GameDetailBean){//展示游戏类型Row({space:10}){ForEach(gameDetail.type.split(' '),(typeItem:string)=>{Text(typeItem).fontSize(14).fontColor(Color.Brown).width(60).height(30).backgroundColor(Color.Orange).borderRadius(8).textAlign(TextAlign.Center)},(typeItem:string)=>typeItem)}//游戏描述Text(gameDetail.desc).maxLines(5).margin(10).width('95%')Button('进入游戏').width('80%')}

预览效果:

在这里插入图片描述

参考

代码仓

https://gitee.com/snowyvalley/harmony-app-dev-basic-course.git

http://www.xdnf.cn/news/6914.html

相关文章:

  • 语音合成终身免费畅用![特殊字符] 紧急提醒:禁用更新锁死权限!
  • 电脑桌面便签软件哪个好用?好用便签Windows版下载推荐
  • 大麦(Hordeum vulgare)中 BAHD 超家族酰基转移酶-文献精读129
  • 关于Android Studio for Platform的使用记录
  • 2025最新的软件测试面试大全(含答案+文档)
  • 系统架构设计(十):结构化编程
  • Linux线程同步信号量
  • hbuilderX 安装Prettier格式化代码
  • 哈希的原理、实现
  • 如何通过交流沟通实现闭环思考模式不断实现自身强效赋能-250517
  • 解决“没有找到有效的sudoers资源,退出”
  • 系分论文《论系统需求分析方法及应用》
  • 【通用智能体】Search Tools:Open Deep Research 项目实战指南
  • Python的re模块:正则表达式处理的魔法棒
  • DeepSeek源码深度解析 × 华为仓颉语言编程精粹——从MoE架构到全场景开发生态
  • 单细胞转录组(1)
  • 【51】快速获取数码管段选表(含小数点)及字母表的工具(分享)
  • 局部放大maya的视图HUD文字大小的方法
  • 五、xlib绘制按钮控件
  • DeepSeek-R1 Supervised finetuning and reinforcement learning (SFT + RL)
  • 怎么在excel单元格1-5行中在原来内容前面加上固定一个字?
  • NVMe简介6之PCIe事务层
  • HTTP与HTTPS协议的核心区别
  • Linux调试生成核心存储文件
  • React Hooks 必须在组件最顶层调用的原因解析
  • Linux517 rsync同步 rsync借xinetd托管 配置yum源回顾
  • 【typenum】 8 常量文件(consts.rs)
  • 第三十五节:特征检测与描述-ORB 特征
  • SummaryWriter 记录和保存训练日志
  • 阿里云服务器跑模型教程