8-游戏详情制作(Navigation组件)
1.1 需求
使用Navigation实现游戏主详情视图,从瀑布流容器中的游戏项(游戏中心首页-游戏瀑布流列表)点击游戏后进入游戏详情页,从游戏详情页可以返回游戏列表主页。
1.2 界面原型
从瀑布流组件进入:

游戏详情:

2 预备知识
2.1 Navigation组件
2.1.1 Navigation路由导航组件
- 实现页面间及组件内部的页面跳转,也可以实现跨包跳转
- 支持传递跳转参数
- 包含导航页和子页
- 导航页根容器是Navigation
- 子页根容器是NavDestination,用于显示Navigation的内容区
- 导航页不存在与页面栈中,与子页,甚至是子页之间通过路由操作进行切换
- Navigation:导航页包含标题栏(包含菜单栏)、内容区和工具栏,hideToolBar(value: boolean)属性用于显隐工具栏
- NavDestination子页包含标题栏,标题栏包含主副标题和返回键,如未设置主副标题并没有返回键时则不显示标题栏,hideTitleBar属性用于显隐标题栏
2.1.2 子页面
NavDestination是Navigation子页面的根容器
页面显示类型
- 标准类型
- NavDestination组件默认为标准类型
- mode属性为NavDestinationMode.STANDARD
- 弹窗类型
- NavDestination设置mode为NavDestinationMode.DIALOG弹窗类型
- 整个NavDestination默认透明显示
2.1.3 NavPathStack路由操作
- NavPathStack路由栈:
- Navigation路由相关的操作都是基于页面栈NavPathStack提供的方法进行
- 每个Navigation都需要创建并传入一个NavPathStack对象
- 页面管理涉及页面跳转、页面返回、页面替换、页面删除、参数获取、路由拦截等功能
- 页面跳转:
NavPathStack通过Push相关的接口去实现页面跳转的功能
- 普通跳转,通过页面的name去跳转,并可以携带param
this.pageStack.pushPath({ name: "PageOne", param: "PageOne Param" })
this.pageStack.pushPathByName("PageOne", "PageOne Param")
- 带返回回调的跳转,跳转时添加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 概述
-
@Provide和@Consume,用于与后代组件的双向数据同步,状态数据实现跨层级传递。(不限于父子,可以是孙辈,穿越能力)
-
通过相同的变量名或者相同的变量别名绑定(建议类型相同,否则会发生类型隐式转换,从而导致应用行为异常)。
-
@Provide装饰的变量,在祖先组件中【提供】信息,@Consume在后代组件中【消费】信息
-
跨组件双向同步
-
@State和@Link组合仅限于父子组件间双向数据同步
-
框架会使用map的形式处理@Provide和@Consume变量,通过map形式传递给当前@Provide所属的所有子组件,子组件在使用@Consume变量时,会从map中查找变量名和别名对应的@Provide变量,并向@Provide注册,所有别名相当于key,必须为string类型
-
更多指导:
https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-provide-and-consume
2.2.2 @Provide装饰器
- 可以使用参数指定别名,指定别名则通过别名绑定变量,未指定别名则通过变量名绑定变量
- 支持类型包括:string、number、boolean、Date、enum、Object、class、Map、Set
- 必须赋初值
- 私有属性,仅可在组件内访问
2.2.3 @Consume装饰器
- 可以使用参数指定别名,指定别名则通过别名匹配变量,未指定别名则通过变量名匹配变量
- 类型需和@Provide保持一致
- 不可赋初值
- 私有属性,仅可在组件内访问
2.3 Flex布局
Flex:弹性布局,以弹性方式布局子组件的容器组件
- direction: 主轴方向,默认:FlexDirection.Row
- Row、RowReverse(从右到左)
- Column、ColumnReverse(从下向上)
- wrap:FlexWrap换行
- NoWrap:默认不换行,超过尺寸会压缩
- Wrap:换行
- WrapReverse:反向换行
- justifyContent、alignItems同线性布局
- alignContent:FlexAlign:多行内容时交叉轴内容对齐
内容参考:
https://developer.huawei.com/consumer/cn/doc/harmonyos-references/ts-container-flex
2.4 自定义组件生命周期&页面生命周期
2.4.1 自定义组件VS页面
- 自定义组件:由@Component装饰的UI单元
- 页面:应用的UI页面,由一个或多个自定义组件构成,@Entry装饰的自定义组件是页面的入口组件,即页面根节点。
2.4.3 自定义组件生命周期
提供以下接口:
- aboutToAppear:组件即将出现时回调,具体时机:创建自定义组件的新实例后,在执行build函数之前执行
- onDidBuild: 组件build函数执行完成之后进行的回调。可用于埋点数据上报等不影响实际UI的功能。
- aboutToDisappear: 自定义组件析构销毁之前执行。
2.4.4 页面生命周期
被@Entry装饰的组件生命周期,提供以下生命周期接口:
- onPageShow:页面每次显示时触发,包括路由过程、应用进入前台等场景
- onPageHide:页面每次隐藏时触发,包括路由过程、应用进入后台等场景
- onBackPress:当用户点击返回按钮时触发
页面生命周期流程:

3 改造导航页
需将GameCenterHome组件改造为导航页,因为要求点击瀑布流组件中的游戏图片,导航到游戏详情页面,改造过程如下:
- 声明路由栈
@Provide('pageInfos') pageInfos: NavPathStack = new NavPathStack();
需要和子页进行路由同步,因此需要使用@Provide装饰器,并通过参数传递别名子页@Consume在使用时,需要与此处别名保持一致
- 将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:
- Navigation的navDestination属性:
navDestination(builder: (name: string, param: unknown) => void)
创建NavDestination组件。使用builder函数,基于name和param构造NavDestination组件。 - @Builder pagesMap(name: string, param:number):
使用条件渲染定义自定义组件作为Navigation的子页(该组件需要使用NavDestination作为根组件)
- 点击瀑布流游戏图片进行导航,并未导航子页传递参数:
//瀑布流组件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:
- 需要接收导航页的路由栈,使用@Consume双向同步,按别名匹配
- 使用NavDestination作为根容器,并设置标题为:游戏详情
- 通过路由栈的getParamByName(‘GameDetail’)获取导航页传递来的参数。
预览效果
从MainPage进入预览:

跳转到导航子页:

5 布局游戏详情
5.1 界面原型

5.2 准备游戏详情数据
- 首先封装游戏详情信息,在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')];}}
- 根据游戏ID返回游戏详情数据,在GameHomeViewModel添加函数getGameDetail:
getGameDetail(id:number):GameDetailBean {let gameDetailBean = new GameDetailBean();return gameDetailBean;}
- 在游戏详情组件上获取游戏详情信息,在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
相关文章:
8-游戏详情制作(Navigation组件)
1.1 需求 使用Navigation实现游戏主详情视图,从瀑布流容器中的游戏项(游戏中心首页-游戏瀑布流列表)点击游戏后进入游戏详情页,从游戏详情页可以返回游戏列表主页。 1.2 界面原型 从瀑布流组件进入: 游戏详情&#…...
Unity引擎源码-物理系统详解-其二
继续我们关于Unity的物理系统的源码阅读,不过这一次我们的目标是PhysX引擎——这个Unity写了一堆脚本来调用API的实际用C写成的底层物理引擎。 Github的地址如下:NVIDIA-Omniverse/PhysX: NVIDIA PhysX SDK (github.com) 下载后发现由三个文件组成&…...
1.3.3 数据共享、汇聚和使用中的安全目标
探索数据共享、汇聚与使用中的安全目标 在当今数字化时代,数据的价值愈发凸显,数据共享、汇聚与使用成为了推动业务发展、促进创新的重要环节。然而,在这一过程中,数据安全至关重要,我们需要明确并保障保密性、完整性…...
【Docker】Docker安装Redis
目录 1.下载镜像 1.1查看下载的镜像 2.创建挂载目录 3.创建容器并启动 4.测试连接 1.下载镜像 根据指令下载镜像文件 docker pull redis#上面指令是下载最新,如需下载指定版本可带版本号 docker pull redis:xxx 响应内容: 1.1查看下载的镜像 下载完…...
Oc语言学习 —— Foundation框架总结
1、NSString类 我们对一个NSString对象赋值的方法是直接将字符串常量赋给对象,例如:NSString *str "hello"; 因为我们的NSString是不可变的,所以我们只能通过一些方法来在我们原来的字符串后面追加或初始化我们的字符串来间接修改…...
react+html2canvas+jspdf将页面导出pdf
主要使用html2canvasjspdf 1.将前端页面导出为pdf 2.处理导出后图表的截断问题 export default function AIReport() {const handleExport async () > {try {// 需要导出的内容idconst element document.querySelector(#AI-REPORT-CONTAINER);if (!element) {message.err…...
LWIP的Socket接口
Socket接口简介 类似于文件操作的一种网络连接接口,通常将其称之为“套接字”。lwIP的Socket接口兼容BSD Socket接口,但只实现完整Socket的部分功能 netconn是对RAW的封装 Socket是对netconn的封装 SOCKET结构体 struct sockaddr { u8_t sa_len; /* 长…...
基于支持向量机(SVM)的P300检测分类
基于支持向量机(SVM)的P300检测分类MATLAB实现,包含数据预处理、特征提取和分类评估流程: %% P300检测分类完整流程(SVM实现) clc; clear; close all;%% 1. 数据加载与模拟生成(实际应用需替换…...
Better Faster Large Language Models via Multi-token Prediction 原理
目录 模型结构: Memory-efficient implementation: 实验: 1. 在大规模模型上效果显著: 2. 在不同类型任务上的效果: 为什么MLP对效果有提升的几点猜测: 1. 并非所有token对生成质量的影响相同 2. 关…...
51c嵌入式※~合集7~Linux
我自己的原文哦~ https://blog.51cto.com/whaosoft/13926843 一、u-boot和bootloader~区别 Bootloader 比Bootloader从字面上来看就是启动加载的意思。用过电脑的都知道,windows开机时会首先加载bios,然后是系统内核,最后启动完毕。那…...
Spring的Validation,这是一套基于注解的权限校验框架
为了保证数据的正确性、完整性,作为一名后端开发工程师,不能仅仅依靠前端来校验数据,还需要对接口请求的参数进行后端的校验。 controller 全局异常处理器 在项目中添加一个全局异常处理器,处理校验异常 RestControllerAdvice p…...
MySQL - 如何突破单库性能瓶颈
数据库服务器硬件优化 我们来看看对数据库所在的服务器是如何进行优化的,服务器是数据库的宿主,其性能直接影响了数据库的性能,所以服务器的优化也是数据库优化的第一步。 数据库服务器通常是从 CPU、内存、磁盘三个角度进行硬件优化的&…...
基于 Vue 和 Node.js 实现图片上传功能:从前端到后端的完整实践
在开发一个社交分享平台时,图片上传功能是核心需求之一。本文将基于一个旅拍社交分享系统,详细解析其图片上传功能的实现原理和技术细节,包括前端处理、后端接收和数据库存储等环节。 1. 前端图片上传实现 在这个项目中,图片上传…...
go封装将所有数字类型转浮点型,可设置保留几位小数
封装转换方法 /* * * 将类型转浮点型,并保留小数 ToFloat(123, 2) ToFloat(3.5254, 0) */ func ToFloat(value interface{}, precision int) (float64, error) {var f float64var err errorswitch v : value.(type) {case float64:f vcase float32:f float64(v)c…...
Rust 学习笔记:关于 Vector 的练习题
Rust 学习笔记:关于 Vector 的练习题 Rust 学习笔记:关于 Vector 的练习题哪个调用会报错?以下代码能否通过编译?若能,输出是?以下代码能否通过编译?若能,输出是?以下代码…...
Linux 系统异常触发后自动重启配置指南
Linux 系统异常触发后自动重启配置指南 一、内核级自动重启配置 适用于内核崩溃(Kernel Panic)、硬件驱动故障等场景,通过 SysRq 和 Watchdog 实现快速恢复。 1. SysRq 强制重启 功能:通过触发内核崩溃或强制重启,绕…...
apisix透传客户端真实IP(real-ip插件)
文章目录 apisix透传客户端真实IP需求和背景apisix real-ip插件为什么需要 trusted_addresses?安全架构的最佳实践 示例场景apisix界面配置 apisix透传客户端真实IP 需求和背景 当 APISIX 前端有其他反向代理(如 Nginx、HAProxy、云厂商的 LBÿ…...
Oracle 数据库的默认隔离级别
Oracle 数据库的默认隔离级别 默认隔离级别:READ COMMITTED Oracle 默认使用 读已提交(READ COMMITTED) 隔离级别,这是大多数OLTP(在线事务处理)系统的标准选择。 官方文档 https://docs.oracle.com/en/database/oracle/oracle-database/19/cncpt/da…...
统计客户端使用情况,使用es存储数据,实现去重以及计数
这篇文件的重点在tshark、filebeat、和logstash。 需求:统计客户使用的客户端版本 实现工具:tshark 1.10.14,filebeat 8.17.0,logstash 8.17.0,elasticsearch 8.17.0,kibana 8.17.0 总体设计:…...
代码随想录算法训练营第六十四天| 图论9—卡码网47. 参加科学大会,94. 城市间货物运输 I
每日被新算法方式轰炸的一天,今天是dijkstra(堆优化版)以及Bellman_ford ,尝试理解中,属于是只能照着代码大概说一下在干嘛。 47. 参加科学大会 https://kamacoder.com/problempage.php?pid1047 dijkstra(…...
oracle序列自增问题
1.先查询表名对应的序列名称 SELECT trigger_name, trigger_type, triggering_event FROM all_triggers WHERE table_name 表名;2. 查询id最大值 SELECT MAX(ID) FROM 表名;3. 查询下一次生成ID SELECT SJCJ_ENERGY_DATA_INSERTID.NEXTVAL FROM DUAL;4. 设置临时步长,越过…...
开启健康生活的多元养生之道
健康养生是一门值得终身学习的学问,在追求健康的道路上,除了常见方法,还有许多容易被忽视却同样重要的角度。掌握这些多元养生之道,能让我们的生活更健康、更有品质。 室内环境的健康不容忽视。定期清洁空调滤网,避…...
【Vite】前端开发服务器的配置
定义一些开发服务器的行为和代理规则 服务器的基本配置 server: {host: true, // 监听所有网络地址port: 8081, // 使用8081端口open: true, // 启动时自动打开浏览器cors: true // 启用CORS跨域支持 } 代理配置 proxy: {/api: {target: https://…...
鸿蒙OSUniApp 制作自定义弹窗与模态框组件#三方框架 #Uniapp
UniApp 制作自定义弹窗与模态框组件 前言 在移动应用开发中,弹窗和模态框是用户交互的重要组成部分,它们用于显示提示信息、收集用户输入或确认用户操作。尽管 UniApp 提供了基础的交互组件如 uni.showModal() 和 uni.showToast(),但这些原…...
Spring Security与Spring Boot集成原理
Spring Security依赖的是过滤器机制,首先是web容器例如tomcat作为独立的产品,本身有自己的一套过滤器机制用来处理请求,那么如何将tomcat接收到的请求转入到Spring Security的处理逻辑呢?spring充分采用了tomcat的拓展机制提供了t…...
VScode各文件转化为PDF的方法
文章目录 代码.py文件.ipynb文本和代码夹杂的文件方法 1:使用 VS Code 插件(推荐)步骤 1:安装必要插件步骤 2:安装 `nbconvert`步骤 3:间接导出(HTML → PDF)本文遇见了系列错误:解决方案:问题原因步骤 1:降级 Jinja2 至兼容版本步骤 2:确保 nbconvert 版本兼容替代…...
精益数据分析(58/126):移情阶段的深度实践与客户访谈方法论
精益数据分析(58/126):移情阶段的深度实践与客户访谈方法论 在创业的漫长旅途中,正确识别和验证问题是成功的第一步。今天,我们继续围绕《精益数据分析》中创业阶段的核心内容,深入探讨移情阶段的关键实践…...
Vue3学习(组合式API——Watch侦听器、watchEffect()详解)
目录 一、Watch侦听器。 (1)侦听单个数据。 (2)侦听多个数据。(数组写法?!) (3)immediate参数。(立即执行回调) (3)deep参数。(深层监…...
【node.js】安装与配置
个人主页:Guiat 归属专栏:node.js 文章目录 1. Node.js简介1.1 Node.js的特点1.2 Node.js架构 2. Node.js安装2.1 下载和安装方法2.1.1 Windows安装2.1.2 macOS安装2.1.3 Linux安装 2.2 使用NVM安装和管理Node.js版本2.2.1 安装NVM2.2.2 使用NVM管理Node…...
《AI大模型应知应会100篇》第62篇:TypeChat——类型安全的大模型编程框架
第62篇:TypeChat——类型安全的大模型编程框架 摘要 在构建 AI 应用时,一个常见的痛点是大语言模型(LLM)输出的不确定性与格式不一致问题。开发者往往需要手动解析、校验和处理模型返回的内容,这不仅增加了开发成本&a…...
