8-游戏详情制作(Navigation组件)

news2025/5/21 4:48:09

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)
  }
  @Builder
  pagesMap(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;//logo
  flowsCount: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.coloradmin.cn/o/2380446.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Unity引擎源码-物理系统详解-其二

继续我们关于Unity的物理系统的源码阅读&#xff0c;不过这一次我们的目标是PhysX引擎——这个Unity写了一堆脚本来调用API的实际用C写成的底层物理引擎。 Github的地址如下&#xff1a;NVIDIA-Omniverse/PhysX: NVIDIA PhysX SDK (github.com) 下载后发现由三个文件组成&…

1.3.3 数据共享、汇聚和使用中的安全目标

探索数据共享、汇聚与使用中的安全目标 在当今数字化时代&#xff0c;数据的价值愈发凸显&#xff0c;数据共享、汇聚与使用成为了推动业务发展、促进创新的重要环节。然而&#xff0c;在这一过程中&#xff0c;数据安全至关重要&#xff0c;我们需要明确并保障保密性、完整性…

【Docker】Docker安装Redis

目录 1.下载镜像 1.1查看下载的镜像 2.创建挂载目录 3.创建容器并启动 4.测试连接 1.下载镜像 根据指令下载镜像文件 docker pull redis#上面指令是下载最新&#xff0c;如需下载指定版本可带版本号 docker pull redis:xxx 响应内容&#xff1a; 1.1查看下载的镜像 下载完…

Oc语言学习 —— Foundation框架总结

1、NSString类 我们对一个NSString对象赋值的方法是直接将字符串常量赋给对象&#xff0c;例如&#xff1a;NSString *str "hello"; 因为我们的NSString是不可变的&#xff0c;所以我们只能通过一些方法来在我们原来的字符串后面追加或初始化我们的字符串来间接修改…

LWIP的Socket接口

Socket接口简介 类似于文件操作的一种网络连接接口&#xff0c;通常将其称之为“套接字”。lwIP的Socket接口兼容BSD Socket接口&#xff0c;但只实现完整Socket的部分功能 netconn是对RAW的封装 Socket是对netconn的封装 SOCKET结构体 struct sockaddr { u8_t sa_len; /* 长…

Better Faster Large Language Models via Multi-token Prediction 原理

目录 模型结构&#xff1a; Memory-efficient implementation&#xff1a; 实验&#xff1a; 1. 在大规模模型上效果显著&#xff1a; 2. 在不同类型任务上的效果&#xff1a; 为什么MLP对效果有提升的几点猜测&#xff1a; 1. 并非所有token对生成质量的影响相同 2. 关…

Spring的Validation,这是一套基于注解的权限校验框架

为了保证数据的正确性、完整性&#xff0c;作为一名后端开发工程师&#xff0c;不能仅仅依靠前端来校验数据&#xff0c;还需要对接口请求的参数进行后端的校验。 controller 全局异常处理器 在项目中添加一个全局异常处理器&#xff0c;处理校验异常 RestControllerAdvice p…

MySQL - 如何突破单库性能瓶颈

数据库服务器硬件优化 我们来看看对数据库所在的服务器是如何进行优化的&#xff0c;服务器是数据库的宿主&#xff0c;其性能直接影响了数据库的性能&#xff0c;所以服务器的优化也是数据库优化的第一步。 数据库服务器通常是从 CPU、内存、磁盘三个角度进行硬件优化的&…

apisix透传客户端真实IP(real-ip插件)

文章目录 apisix透传客户端真实IP需求和背景apisix real-ip插件为什么需要 trusted_addresses&#xff1f;安全架构的最佳实践 示例场景apisix界面配置 apisix透传客户端真实IP 需求和背景 当 APISIX 前端有其他反向代理&#xff08;如 Nginx、HAProxy、云厂商的 LB&#xff…

Oracle 数据库的默认隔离级别

Oracle 数据库的默认隔离级别 默认隔离级别&#xff1a;READ COMMITTED Oracle 默认使用 读已提交(READ COMMITTED) 隔离级别&#xff0c;这是大多数OLTP(在线事务处理)系统的标准选择。 官方文档 https://docs.oracle.com/en/database/oracle/oracle-database/19/cncpt/da…

代码随想录算法训练营第六十四天| 图论9—卡码网47. 参加科学大会,94. 城市间货物运输 I

每日被新算法方式轰炸的一天&#xff0c;今天是dijkstra&#xff08;堆优化版&#xff09;以及Bellman_ford &#xff0c;尝试理解中&#xff0c;属于是只能照着代码大概说一下在干嘛。 47. 参加科学大会 https://kamacoder.com/problempage.php?pid1047 dijkstra&#xff08…

开启健康生活的多元养生之道

健康养生是一门值得终身学习的学问&#xff0c;在追求健康的道路上&#xff0c;除了常见方法&#xff0c;还有许多容易被忽视却同样重要的角度。掌握这些多元养生之道&#xff0c;能让我们的生活更健康、更有品质。​ 室内环境的健康不容忽视。定期清洁空调滤网&#xff0c;避…

【Vite】前端开发服务器的配置

定义一些开发服务器的行为和代理规则 服务器的基本配置 server: {host: true, // 监听所有网络地址port: 8081, // 使用8081端口open: true, // 启动时自动打开浏览器cors: true // 启用CORS跨域支持 } 代理配置 proxy: {/api: {target: https://…

Spring Security与Spring Boot集成原理

Spring Security依赖的是过滤器机制&#xff0c;首先是web容器例如tomcat作为独立的产品&#xff0c;本身有自己的一套过滤器机制用来处理请求&#xff0c;那么如何将tomcat接收到的请求转入到Spring Security的处理逻辑呢&#xff1f;spring充分采用了tomcat的拓展机制提供了t…

VScode各文件转化为PDF的方法

文章目录 代码.py文件.ipynb文本和代码夹杂的文件方法 1:使用 VS Code 插件(推荐)步骤 1:安装必要插件步骤 2:安装 `nbconvert`步骤 3:间接导出(HTML → PDF)本文遇见了系列错误:解决方案:问题原因步骤 1:降级 Jinja2 至兼容版本步骤 2:确保 nbconvert 版本兼容替代…

Vue3学习(组合式API——Watch侦听器、watchEffect()详解)

目录 一、Watch侦听器。 &#xff08;1&#xff09;侦听单个数据。 &#xff08;2&#xff09;侦听多个数据。&#xff08;数组写法&#xff1f;&#xff01;&#xff09; &#xff08;3&#xff09;immediate参数。(立即执行回调) &#xff08;3&#xff09;deep参数。(深层监…

【node.js】安装与配置

个人主页&#xff1a;Guiat 归属专栏&#xff1a;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篇&#xff1a;TypeChat——类型安全的大模型编程框架 摘要 在构建 AI 应用时&#xff0c;一个常见的痛点是大语言模型&#xff08;LLM&#xff09;输出的不确定性与格式不一致问题。开发者往往需要手动解析、校验和处理模型返回的内容&#xff0c;这不仅增加了开发成本&a…

EdgeShard:通过协作边缘计算实现高效的 LLM 推理

(2024-05-23) EdgeShard: Efficient LLM Inference via Collaborative Edge Computing (EdgeShard:通过协作边缘计算实现高效的 LLM 推理) 作者: Mingjin Zhang; Jiannong Cao; Xiaoming Shen; Zeyang Cui;期刊: (发表日期: 2024-05-23)期刊分区:本地链接: Zhang 等 - 2024 …

火山 RTC 引擎9 ----集成 appkey

一、集成 appkey 1、网易RTC 初始化过程 1&#xff09;、添加头文件 实现互动直播 - 互动直播 2.0网易云信互动直播产品的基本功能包括音视频通话和连麦直播&#xff0c;当您成功初始化 SDK 之后&#xff0c;您可以简单体验本产品的基本业务流程&#xff0c;例如主播加入房间…