项目背景
在日常开发中,影视类应用是常见的练习项目,涉及列表渲染、状态管理、多Tab切换、数据持久化等核心知识点。本文将通过 HarmonyOS ArkTS 实现一个功能完整的电影追剧与观影记录应用,涵盖从数据结构设计到完整页面布局的全流程,并提供可直接运行的完整源码。
技术栈 :HarmonyOS SDK(API 24+) + ArkTS + DevEco Studio
适用读者:具备 ArkTS 基础,希望提升实战能力的开发者
项目效果
本文使用 HarmonyOS 和 ArkTS 实现一个电影追剧与观影记录应用。功能完整,UI 精美,深蓝影院主题,交互流畅,采用卡片式布局和渐变色设计。
项目使用 DevEco Studio 开发,适配 API 24 及以上版本。
运行效果

功能介绍
- ✅ 电影/电视剧分类浏览
- ✅ 正在热映
- ✅ 观影记录
- ✅ 想看/看过/在看标记
- ✅ 五星评分
- ✅ 电影详情和简介
- ✅ 片单管理
- ✅ 观影统计
- ✅ 电影搜索
- ✅ 演员信息
- ✅ 上映提醒
- ✅ 短评记录
- ✅ 观影时长统计
- ✅ 电影推荐
- ✅ 海报墙
定义数据结构
首先定义核心数据模型 Movie,这是整个应用的数据基石。设计时需要兼顾功能需求与 ArkTS 的状态管理特性------建议使用 interface 而非 class,以充分利用 @State 和 @Observed 的响应式能力。
typescript
interface Movie {
id: number;
title: string;
poster: string;
rating: number;
year: number;
genre: string[];
description: string;
status: 'want' | 'watching' | 'watched';
myRating?: number;
}
字段说明:
id:唯一标识,用于列表渲染的keystatus:观影状态,使用联合类型限制取值,提高类型安全性myRating?:用户自己的评分,可选字段,配合条件渲染显示
初始化页面状态
使用 @State 装饰器管理页面响应式数据。这里初始化了两条示例数据用于展示效果,实际项目中可从云端 API 或本地数据库加载。
typescript
@State private movies: Movie[] = [
{
id: 1,
title: '流浪地球2',
poster: '🎬',
rating: 8.3,
year: 2023,
genre: ['科幻', '灾难'],
description: '太阳即将毁灭,人类在地球表面建造出巨大的推进器,带着地球一起寻找新家园。',
status: 'watched',
myRating: 9
},
{
id: 2,
title: '满江红',
poster: '🎭',
rating: 7.0,
year: 2023,
genre: ['悬疑', '喜剧'],
description: '南宋绍兴年间,岳飞死后四年,秦桧率领朝廷使团前往山西与金国和谈。',
status: 'want'
},
];
@State private currentTab: number = 0;
核心功能方法
以下是实现业务逻辑的核心方法。这里展示完整的增删改和状态切换逻辑,每个方法都直接操作 @State 数组,ArkTS 会自动触发 UI 更新。
typescript
// 添加电影记录
private addMovie(movie: Movie): void {
// 生成新 ID:取当前最大 ID + 1
const maxId = this.movies.length > 0
? Math.max(...this.movies.map(m => m.id))
: 0;
this.movies = [...this.movies, { ...movie, id: maxId + 1 }];
}
// 切换观影状态(想看 → 在看 → 看过)
private toggleStatus(id: number): void {
this.movies = this.movies.map(movie => {
if (movie.id !== id) return movie;
const nextStatus: Record<string, Movie['status']> = {
'want': 'watching',
'watching': 'watched',
'watched': 'want'
};
return { ...movie, status: nextStatus[movie.status] };
});
}
// 删除电影记录
private deleteMovie(id: number): void {
this.movies = this.movies.filter(movie => movie.id !== id);
}
// 更新用户评分
private updateRating(id: number, rating: number): void {
this.movies = this.movies.map(movie =>
movie.id === id ? { ...movie, myRating: rating } : movie
);
}
最佳实践提示 :操作 @State 数组时,始终创建新数组 (使用 map、filter、展开运算符等),而不是 push/splice 等原地修改。这样 ArkTS 的依赖追踪才能正确检测到变化并触发 UI 重绘。
@Builder 可复用组件
使用 @Builder 抽取可复用的电影卡片组件,减少重复代码,提升可维护性。这里展示了完整的卡片布局,包括海报、信息、状态标签和操作按钮。
typescript
@Builder
ItemCard(item: Movie) {
Column() {
// 海报区域
Text(item.poster)
.fontSize(50)
.width('100%')
.height(160)
.textAlign(TextAlign.Center)
.backgroundColor('#312E81')
.borderRadius(8)
// 电影信息
Column({ space: 4 }) {
Text(item.title)
.fontSize(14)
.fontColor(Color.White)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
Row({ space: 4 }) {
Text('⭐').fontSize(10)
Text(`${item.rating}`).fontSize(12).fontColor('#FBBF24')
Blank()
Text(item.status === 'watched' ? '✅' : item.status === 'watching' ? '🎬' : '📌')
.fontSize(14)
}.width('100%')
}
.padding({ left: 6, right: 6, bottom: 8 })
}
.backgroundColor('#1E293B')
.borderRadius(12)
.shadow({ radius: 4, color: '#00000040', offsetX: 0, offsetY: 2 })
.onClick(() => {
// 点击卡片可触发详情跳转
this.toggleStatus(item.id);
})
}
完整 build() 页面布局
将所有组件组合成完整页面。布局结构为:顶部导航栏 → 搜索栏 → Tab 切换栏 → 内容区(使用 Tabs 组件)。
typescript
build() {
Column() {
// 顶部导航栏
Row() {
Text('观影记录')
.fontSize(24)
.fontWeight(FontWeight.Bold)
.fontColor(Color.White)
Blank()
Row({ space: 16 }) {
Text('🔍').fontSize(22).fontColor(Color.White)
Text('🔔').fontSize(22).fontColor(Color.White)
}
}
.width('100%')
.padding({ left: 20, right: 20, top: 12, bottom: 12 })
.backgroundColor('#1E3A8A')
// 搜索栏(视觉占位,可扩展实现搜索功能)
Row() {
Text('🔍 搜索电影、电视剧、演员...')
.fontSize(14)
.fontColor(Color.Gray)
.layoutWeight(1)
}
.width('100%')
.height(40)
.backgroundColor('#334155')
.borderRadius(8)
.margin({ left: 16, right: 16, top: 8 })
.padding({ left: 12 })
// Tab 切换区域
Tabs({ barPosition: BarPosition.Start }) {
TabContent() {
Scroll() {
Grid() {
ForEach(this.movies, (movie: Movie) => {
GridItem() {
this.ItemCard(movie)
}
}, (movie: Movie) => movie.id.toString())
}
.columnsTemplate('1fr 1fr 1fr')
.columnsGap(10)
.rowsGap(16)
.width('100%')
.padding(16)
}
.scrollBar(BarState.Off)
}
.tabBar('发现')
TabContent() {
Column()
.width('100%')
.height('100%')
.backgroundColor('#1E293B')
.justifyContent(FlexAlign.Center)
Text('🎬 创建你的专属片单')
.fontSize(16)
.fontColor('#94A3B8')
}
.tabBar('片单')
TabContent() {
Column()
.width('100%')
.height('100%')
.backgroundColor('#1E293B')
.justifyContent(FlexAlign.Center)
Text('👤 我的观影记录')
.fontSize(16)
.fontColor('#94A3B8')
}
.tabBar('我的')
}
.width('100%')
.layoutWeight(1)
.barHeight(50)
.backgroundColor('#1E3A8A')
}
.width('100%')
.height('100%')
.backgroundColor('#0F172A')
}
关键设计点:
ForEach使用movie.id.toString()作为 key,确保列表更新高效- 使用
Blank()实现弹性空白布局 - 深色主题配色
#0F172A→#1E3A8A→#312E81,层次递进
页面设计说明
设计系统:
- 主色 :
#1E3A8A(深海蓝,用于顶部导航和 Tab 栏) - 辅色 :
#60A5FA(浅蓝,用于强调和高亮) - 背景 :各层级分别采用
#0F172A/#1E293B/#334155构建清晰视觉层次 - 卡片:圆角 12px,搭配柔和阴影,营造浮层效果
- 字体:标题 24px Bold,正文 13-14px,保持阅读舒适度
SDK 配置
确保 build-profile.json5 中的 SDK 版本配置正确:
json5
{
"apiType": "stageMode",
"buildOption": {},
"targets": [
{
"name": "default",
"runtimeOS": "HarmonyOS"
}
]
}
compileSdkVersion:API 24compatibleSdkVersion:"6.1.1(24)"
运行项目
- 使用 DevEco Studio 创建新的 ArkTS 项目(API 24)
- 将上述代码复制到
entry/src/main/ets/pages/Index.ets - 确保
entry/src/main/ets/entryability/EntryAbility.ets中路由指向pages/Index - 点击运行,即可在模拟器或真机上预览效果
小贴士 :如果模拟器上没有数据展示,请检查
ForEach的movies数组是否正确初始化。
项目总结
本项目从零实现了电影追剧与观影记录应用的完整功能,核心收获:
- ArkTS 状态管理 :熟练使用
@State和数组不可变更新模式 - 组件化思想 :通过
@Builder抽取可复用 UI,代码更整洁 - 深色主题设计:掌握了多层级背景色搭配和卡片式设计技巧
- 复杂布局 :
Column+Row+Grid+Tabs+Scroll的组合运用
进阶方向建议:
- 接入真实 API(如豆瓣电影、TMDB)获取动态数据
- 使用
@Observed+@ObjectLink实现更细粒度的响应式更新 - 添加路由导航实现电影详情页
- 集成 Preferences 或 RelationalStore 实现本地数据持久化
- 使用
@Watch监听评分变化自动更新统计
**完整源码可在上方的代码块中获取,直接复制到 DevEco Studio 即可运行!**等。