鸿蒙原生应用实战(三):游戏详情页与交互功能 — 400行ArkTS深度实践
·
鸿蒙原生应用实战(三):游戏详情页与交互功能 — 400行ArkTS深度实践
一、前言
游戏详情页是整个 App 最复杂的页面,共计 444 行 ArkTS 代码,包含 11 个功能模块。本篇将深入剖析详情页的完整开发过程,涵盖:
- 全屏渐变色 Header 设计
- 游客观数据面板
- 进度条与开发者信息
- 游戏简介排版
- 状态切换与评分交互
- TextArea 评价输入框
- 成就列表系统
- 横向滚动"相似推荐"
- 用户评测展示
二、页面整体布局
┌─────────────────────────────────┐
│ ← ··· (全屏) │
│ 🎮 │
│ 艾尔登法环 │ ← 渐变 Header (190px)
│ PC · 动作RPG · 2022-02-25 │
├─────────────────────────────────┤
│ 49 186h 38/42 通关 │ ← 数据面板
├─────────────────────────────────┤
│ ████████░░░░░░ 100% │ ← 进度条
├─────────────────────────────────┤
│ [游戏截图占位图] │ ← 截图区(预留)
├─────────────────────────────────┤
│ 开发商: FromSoftware │ ← 开发者信息
│ 发行商: Bandai Namco │
│ 发行日期: 2022-02-25 │
├─────────────────────────────────┤
│ 游戏简介 │ ← 简介区
│ ······ │
├─────────────────────────────────┤
│ 我的状态 │
│ [想玩] [在玩] [通关] │ ← 交互区
│ 我的评分: ⭐⭐⭐⭐⭐ │
│ [📝 写评价] │
│ ┌────────────────────────┐ │
│ │ TextArea 输入框 │ │ ← 评价输入(展开)
│ └────────────────────────┘ │
├─────────────────────────────────┤
│ 🏅 成就列表 │ ← 成就系统
│ 🏆 史诗级游戏收藏家 🔒 │
│ 🏆 白金达人 🔒 │
├─────────────────────────────────┤
│ 🎯 你可能也喜欢 │ ← 相似推荐(横向)
│ [只狼] [黑暗之魂3] [仁王2] │
├─────────────────────────────────┤
│ 💬 用户评测 │ ← 评测展示
│ ⭐⭐⭐⭐⭐ "神作!" │
└─────────────────────────────────┘
三、扩展数据模型
详情页需要比列表页更丰富的数据,我们新增 GameDetail 接口:
interface GameDetail {
id: number;
title: string;
platform: string;
genre: string;
status: string;
rating: number;
hours: number;
progress: number;
coverColor: string;
developer: string; // 开发商
publisher: string; // 发行商
releaseDate: string; // 发行日期
description: string; // 游戏简介
achievements: number; // 已获得成就
totalAchievements: number; // 成就总数
}
与 GameItem 的区别:
- 新增 6 个字段:
developer,publisher,releaseDate,description,achievements,totalAchievements - 承载详情展示所需的所有信息
四、状态管理
struct GameDetailPage {
@State gameId: number = -1;
@State game: GameDetail | null = null;
@State myStatus: string = ''; // 用户自选状态
@State myRating: number = 0; // 用户评分 (0-50)
@State showReviewBox: boolean = false; // 是否展开评价框
@State userReview: string = ''; // 评价内容
}
aboutToAppear 生命周期:
aboutToAppear(): void {
const params = router.getParams() as Record<string, Object>;
if (params && params['gameId'] !== undefined) {
this.gameId = params['gameId'] as number;
}
this.loadGame();
}
五、核心功能模块实现
5.1 数据加载与查找
loadGame(): void {
const allGames: GameDetail[] = [
{
id: 1,
title: '艾尔登法环',
platform: 'PC',
genre: '动作RPG',
status: '通关',
rating: 49,
hours: 186,
progress: 100,
coverColor: '#FFD700',
developer: 'FromSoftware',
publisher: 'Bandai Namco',
releaseDate: '2022-02-25',
description: '以黑暗奇幻世界为舞台的硬核动作RPG。玩家将穿越"狭间之地",挑战半神与古老之王,最终成为艾尔登之王。广阔的开放世界、深度的角色养成和极具挑战性的战斗系统令人着迷。',
achievements: 38,
totalAchievements: 42
}
// ... 更多游戏数据
];
// 根据路由参数查找对应游戏
for (let i: number = 0; i < allGames.length; i++) {
if (allGames[i].id === this.gameId) {
this.game = allGames[i];
this.myStatus = allGames[i].status;
this.myRating = allGames[i].rating;
break;
}
}
// 兜底逻辑:未找到时显示第一条数据
if (!this.game) {
this.game = allGames[0];
}
}
注意:ArkTS 中
for循环的计数变量必须显式声明类型let i: number。
5.2 全屏渐变 Header ⭐
这是详情页最亮眼的视觉设计:
@Builder buildHeader() {
Stack() {
// 背景色块(使用游戏封面颜色)
Column()
.width('100%').height(190)
.backgroundColor(this.game ? this.game.coverColor : '#333333')
.justifyContent(FlexAlign.Center)
Column() {
// 顶部导航行(返回 + 更多)
Row() {
Text('←').fontSize(22).fontColor(Color.White)
.onClick(() => { router.back(); })
Blank()
Text('···').fontSize(22).fontColor(Color.White)
}
.width('100%').padding({ left: 16, right: 16 })
.position({ top: 40 })
// 游戏信息
if (this.game) {
Column() {
Text('🎮').fontSize(48)
Text(this.game.title)
.fontSize(20).fontWeight(FontWeight.Bold)
.fontColor(Color.White).margin({ top: 4 })
Text(`${this.game.platform} · ${this.game.genre} · ${this.game.releaseDate}`)
.fontSize(12).fontColor('rgba(255,255,255,0.8)').margin({ top: 4 })
}
.alignItems(HorizontalAlign.Center)
.width('100%').position({ top: 75 })
}
}
.width('100%').height('100%')
}
.width('100%').height(190)
}
设计要点:
- 使用
Stack实现层叠布局:背景层 + 内容层 - 背景色取自游戏封面色,形成品牌一致性
- 文字颜色使用
rgba(255,255,255,0.8)半透明白,增强层次感 - 返回按钮用
position({ top: 40 })固定在安全区域 - 游戏标题居中显示在大约 75px 位置(顶部留白给导航行)
5.3 数据面板
@Builder buildInfoSection() {
if (this.game) {
Row() {
Column() {
Text(this.game.rating.toString())
.fontSize(22).fontWeight(FontWeight.Bold).fontColor('#FF6B35')
Text('评分(媒体)').fontSize(10).fontColor('#999999').margin({ top: 2 })
}.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() {
Text(this.game.hours.toString())
.fontSize(22).fontWeight(FontWeight.Bold).fontColor('#3498DB')
Text('游玩时长(h)').fontSize(10).fontColor('#999999').margin({ top: 2 })
}.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() {
Text(`${this.game.achievements}/${this.game.totalAchievements}`)
.fontSize(18).fontWeight(FontWeight.Bold).fontColor('#2ECC71')
Text('成就进度').fontSize(10).fontColor('#999999').margin({ top: 2 })
}.layoutWeight(1).alignItems(HorizontalAlign.Center)
Column() {
Text(this.game.status)
.fontSize(14).fontWeight(FontWeight.Bold)
.fontColor(this.getStatusColor(this.game.status))
Text('游玩状态').fontSize(10).fontColor('#999999').margin({ top: 2 })
}.layoutWeight(1).alignItems(HorizontalAlign.Center)
}
.width('100%').padding(14).backgroundColor('#FFFFFF')
.margin({ top: 8, left: 16, right: 16 }).borderRadius(10)
}
}
四个数据卡片:评分 / 时长 / 成就 / 状态,使用 layoutWeight(1) 均分宽度。
5.4 状态颜色映射
getStatusColor(status: string): ResourceStr {
if (status === '通关') return '#2ECC71'; // 绿色
if (status === '在玩') return '#3498DB'; // 蓝色
if (status === '想玩') return '#9B59B6'; // 紫色
return '#999999'; // 灰色
}
5.5 开发者信息行
三列并排布局,显示开发商、发行商、发行日期:
@Builder buildDevInfo() {
if (this.game) {
Row() {
Column() {
Text('开发商').fontSize(11).fontColor('#999999')
Text(this.game.developer)
.fontSize(13).fontColor('#333333').margin({ top: 2 })
}.layoutWeight(1).alignItems(HorizontalAlign.Start)
Column() {
Text('发行商').fontSize(11).fontColor('#999999')
Text(this.game.publisher)
.fontSize(13).fontColor('#333333').margin({ top: 2 })
}.layoutWeight(1).alignItems(HorizontalAlign.Start)
Column() {
Text('发行日期').fontSize(11).fontColor('#999999')
Text(this.game.releaseDate)
.fontSize(13).fontColor('#333333').margin({ top: 2 })
}.layoutWeight(1).alignItems(HorizontalAlign.Start)
}
// ...
}
}
5.6 状态控制与评分交互 ⭐⭐
这是详情页交互最丰富的区域,包含三个子功能:
@Builder buildStatusControl() {
Column() {
Text('我的状态').fontSize(15).fontWeight(FontWeight.Bold).fontColor('#1A1A2E')
// 状态切换按钮组
Row() {
ForEach(['想玩', '在玩', '通关'], (s: string) => {
Text(s)
.fontSize(13)
.fontColor(this.myStatus === s ? '#FFFFFF' : '#666666')
.padding({ left: 20, right: 20, top: 6, bottom: 6 })
.backgroundColor(this.myStatus === s ? '#FF6B35' : '#F0F0F0')
.borderRadius(16).margin({ right: 10 })
.onClick(() => { this.myStatus = s; })
}, (s: string) => s)
}.width('100%').margin({ top: 8 })
// 星级评分
Row() {
Text('我的评分:').fontSize(13).fontColor('#666666')
ForEach([1, 2, 3, 4, 5], (star: number) => {
Text(star <= Math.round(this.myRating / 10) ? '⭐' : '☆')
.fontSize(18)
.onClick(() => { this.myRating = star * 10; })
}, (star: number) => star.toString())
}.width('100%').margin({ top: 8 })
// 展开/收起评价框
Row() {
Text(this.showReviewBox ? '📝 收起评价' : '📝 写评价')
.fontSize(13).fontColor('#FF6B35')
.onClick(() => { this.showReviewBox = !this.showReviewBox; })
}.width('100%').margin({ top: 8 })
// 条件渲染:评价输入框
if (this.showReviewBox) {
TextArea({ placeholder: '写下你的游戏评价...', text: this.userReview })
.width('100%').height(80).backgroundColor('#F5F5F5')
.borderRadius(8).fontSize(13).padding(8).margin({ top: 6 })
.onChange((v: string) => { this.userReview = v; })
}
}
// ...
}
交互逻辑详解:
| 功能 | 实现方式 | 交互反馈 |
|---|---|---|
| 状态切换 | 点击更新 myStatus,三选一 |
选中态变橙色高亮 |
| 星级评分 | 点击更新 myRating,存储值为 10/20/30/40/50 |
实心/空心星切换 |
| 评价框 | 点击展开 TextArea,再点击收起 |
展开/收起动画 |
评分系统映射:
用户点 1 星 → myRating = 10 → 对应 1/5 分制
用户点 5 星 → myRating = 50 → 对应 5/5 分制
5.7 成就列表系统
成就列表使用二维数组存储,每种成就有标题、描述、解锁状态:
@Builder buildAchievementList() {
Column() {
Text('🏅 成就列表')
.fontSize(15).fontWeight(FontWeight.Bold).fontColor('#1A1A2E')
.width('100%')
const achievementsData: string[][] = [
['史诗级游戏收藏家', '收藏10款游戏', '🔒'],
['白金达人', '完成一款游戏全部成就', '🔒'],
['初心者', '添加第一款游戏', '✅'],
['游戏鉴赏家', '评价5款游戏', '✅'],
['全平台制霸', '在3个不同平台拥有游戏', '🔒'],
['夜猫子', '凌晨2点后还在玩游戏', '✅']
];
ForEach(achievementsData, (ach: string[]) => {
Row() {
Text(ach[2] === '✅' ? '🏆' : '🏅')
.fontSize(18)
.grayscale(ach[2] === '✅' ? 0 : 1) // 未解锁=灰色
Column() {
Text(ach[0]).fontSize(13).fontWeight(FontWeight.Medium)
.fontColor(ach[2] === '✅' ? '#333333' : '#BBBBBB')
Text(ach[1]).fontSize(11).fontColor('#999999').margin({ top: 2 })
}
.alignItems(HorizontalAlign.Start).margin({ left: 10 }).layoutWeight(1)
Text(ach[2]).fontSize(14) // "🔒" 或 "✅"
}
.width('100%').padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor('#FFFFFF')
}, (ach: string[]) => ach[0])
}
.width('100%').backgroundColor('#FFFFFF').margin({ top: 8 })
}
设计亮点:
- 已解锁成就:金色彩色奖杯
🏆+ 深色文字 - 未解锁成就:灰色调奖牌
🏅+ 浅灰文字 +grayscale(1)滤镜 🔒/✅符号在行尾直观显示状态
5.8 相似推荐(横向滚动)
@Builder buildSimilarGames() {
Column() {
Text('🎯 你可能也喜欢')
.fontSize(15).fontWeight(FontWeight.Bold).fontColor('#1A1A2E')
.width('100%').padding({ left: 16 })
Scroll() {
Row() {
const similarGames: string[][] = [
['只狼:影逝二度', '动作', '#E74C3C'],
['黑暗之魂3', '动作RPG', '#2C3E50'],
['仁王2', '动作', '#FF6B35']
];
ForEach(similarGames, (s: string[]) => {
Column() {
// 封面占位块
Column().width(90).height(60).borderRadius(6)
.backgroundColor(s[2] as string)
Text('🎮').fontSize(24)
Text(s[0]).fontSize(11).fontColor('#333333').margin({ top: 4 })
.maxLines(1).textOverflow({ overflow: TextOverflow.Ellipsis })
.width(90)
Text(s[1]).fontSize(10).fontColor('#999999')
}
.margin({ right: 10 })
}, (s: string[]) => s[0])
}
.padding({ left: 16 })
}
.scrollable(ScrollDirection.Horizontal).height(110)
}
// ...
}
5.9 用户评测系统
展示三条模拟评测,包含头像、用户名、星级、内容和时间:
@Builder buildReviewSection() {
Column() {
Text('💬 用户评测')
.fontSize(15).fontWeight(FontWeight.Bold).fontColor('#1A1A2E')
.width('100%').padding({ left: 16 })
const reviewData: string[][] = [
['游戏达人', '⭐⭐⭐⭐⭐', '神作!无论是画面、剧情还是战斗系统都无可挑剔!', '2天前'],
['休闲玩家', '⭐⭐⭐⭐', '很好玩但难度较高,手残党需要多练习。', '1周前'],
['收集控', '⭐⭐⭐⭐⭐', '全成就达成!200+小时的极致体验。', '2周前']
];
ForEach(reviewData, (review: string[]) => {
Column() {
Row() {
// 用户头像(取用户名首字)
Stack() {
Column().width(28).height(28).borderRadius(14)
.backgroundColor('#FF6B35')
.justifyContent(FlexAlign.Center)
Text(review[0].charAt(0)).fontSize(14).fontColor(Color.White)
}
Text(review[0]).fontSize(13).fontWeight(FontWeight.Medium)
.fontColor('#333333').margin({ left: 8 })
Blank()
Text(review[3]).fontSize(11).fontColor('#BBBBBB')
}.width('100%')
Text(review[1]).fontSize(14).margin({ top: 4 })
Text(review[2]).fontSize(12).fontColor('#666666')
.lineHeight(18).width('100%').margin({ top: 4 })
}
.width('100%').padding({ left: 16, right: 16, top: 8, bottom: 8 })
.backgroundColor('#FFFFFF')
}, (review: string[]) => review[0])
}
.width('100%').backgroundColor('#FFFFFF').margin({ top: 8 })
}
5.10 截图区域(预留)
@Builder buildScreenshotGallery() {
Stack() {
Column().width('100%').height(140)
.backgroundColor('#F0F0F0').borderRadius(8)
Column() {
Text('🎮 游戏截图').fontSize(13).fontColor('#999999')
Text('(截图功能预留)').fontSize(11).fontColor('#BBBBBB').margin({ top: 4 })
}
}
.width('100%').height(140)
.margin({ top: 8, left: 16, right: 16 })
}
预留占位区域,后续可接入图片懒加载或 gallery 组件。
六、布局组装
build(): void {
Column() {
Scroll() {
Column() {
this.buildHeader() // 全屏 Header
this.buildInfoSection() // 数据面板
this.buildProgress() // 进度条
this.buildScreenshotGallery() // 截图区
this.buildDevInfo() // 开发者信息
this.buildDescription() // 游戏简介
this.buildStatusControl() // 状态 + 评分 + 评价
this.buildAchievementList() // 成就列表
this.buildSimilarGames() // 相似推荐
this.buildReviewSection() // 用户评测
}
.width('100%').padding({ bottom: 30 })
}
.scrollable(ScrollDirection.Vertical)
.layoutWeight(1).width('100%')
}
.width('100%').height('100%').backgroundColor('#F5F5F5')
}
七、444行代码的分层架构
整个详情页的代码组织遵循清晰的分层:
| 代码段 | 行数 | 职责 |
|---|---|---|
| Interface + State | L1-30 | 数据模型与状态变量 |
| loadGame() | L31-58 | 数据加载与路由参数解析 |
| @Builder × 11 | L67-416 | 11 个 UI 构建函数 |
| build() | L418-444 | 页面布局组装 |
每个 @Builder 方法控制在 20-40 行,职责单一,可读性强。
八、与列表页的联动
从列表页跳转到详情页的完整数据流:
GameListPage GameDetailPage
│ │
│ router.pushUrl({ │
│ url: 'pages/GameDetailPage', │
│ params: { gameId: game.id } │
│ }) │
│ ───────────────────────────────► │
│ │
│ aboutToAppear()
│ │
│ ▼
│ router.getParams()
│ │
│ ▼
│ loadGame()
│ 查找 gameId
│ │
│ ▼
│ @State 更新
│ 触发 UI 刷新

九、小结
本篇我们完成了详情页的全部 11 个功能模块开发:
- ✅ 全屏渐变 Header + 游戏信息展示
- ✅ 四维数据面板(评分/时长/成就/状态)
- ✅ 线性进度条
- ✅ 开发者信息三列布局
- ✅ 游戏简介排版
- ✅ 状态切换按钮组 + 星级评分 + TextArea 评价
- ✅ 成就列表系统(解锁/未解锁视觉区分)
- ✅ 横向滚动相似推荐
- ✅ 用户评测展示
下一篇将开发 愿望单页面 和 个人统计页面,实现数据聚合与可视化展示。
系列目录:
- 第一篇:项目搭建与首页开发
- 第二篇:游戏库列表与筛选排序
- 第三篇:游戏详情页与交互功能(本文)
- 第四篇:愿望单与个人统计
- 第五篇:路由导航与工程优化
更多推荐




所有评论(0)