HarmonyOS ArkTS 实战:实现一个电影追剧与观影记录应用(完整源码)

项目背景

在日常开发中,影视类应用是常见的练习项目,涉及列表渲染、状态管理、多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:唯一标识,用于列表渲染的 key
  • status:观影状态,使用联合类型限制取值,提高类型安全性
  • 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 数组时,始终创建新数组 (使用 mapfilter、展开运算符等),而不是 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 24
  • compatibleSdkVersion:"6.1.1(24)"

运行项目

  1. 使用 DevEco Studio 创建新的 ArkTS 项目(API 24)
  2. 将上述代码复制到 entry/src/main/ets/pages/Index.ets
  3. 确保 entry/src/main/ets/entryability/EntryAbility.ets 中路由指向 pages/Index
  4. 点击运行,即可在模拟器或真机上预览效果

小贴士 :如果模拟器上没有数据展示,请检查 ForEachmovies 数组是否正确初始化。

项目总结

本项目从零实现了电影追剧与观影记录应用的完整功能,核心收获:

  1. ArkTS 状态管理 :熟练使用 @State 和数组不可变更新模式
  2. 组件化思想 :通过 @Builder 抽取可复用 UI,代码更整洁
  3. 深色主题设计:掌握了多层级背景色搭配和卡片式设计技巧
  4. 复杂布局Column + Row + Grid + Tabs + Scroll 的组合运用

进阶方向建议

  • 接入真实 API(如豆瓣电影、TMDB)获取动态数据
  • 使用 @Observed + @ObjectLink 实现更细粒度的响应式更新
  • 添加路由导航实现电影详情页
  • 集成 Preferences 或 RelationalStore 实现本地数据持久化
  • 使用 @Watch 监听评分变化自动更新统计

**完整源码可在上方的代码块中获取,直接复制到 DevEco Studio 即可运行!**等。

相关推荐
●VON1 小时前
鸿蒙 PC Markdown 编辑器自由窗口:覆盖侧栏与响应式预算
安全·华为·编辑器·harmonyos·鸿蒙
SameX1 小时前
ArkTS 用 Preferences 存 App 配置的正确姿势 —— 从踩坑到 singleton 封装
harmonyos
懿路向前2 小时前
【HarmonyOS学习笔记】2026-07-24 | textProcessing 实体识别与踩坑实录
笔记·学习·边缘计算·harmonyos
贾伟康2 小时前
【笔下生辉|02】HarmonyOS ArkTS 素材库详情实战:组织例句、解释、收藏和练习入口
harmonyos·arkts·详情页·学习进度·收藏功能
独隅2 小时前
DevEco Code 在 Windows/MacOS 双系统上的完整使用指南
ide·人工智能·windows·macos·华为·harmonyos
●VON3 小时前
鸿蒙 PC Markdown 编辑器有界版本历史:沙箱快照、完整性校验与安全恢复
安全·华为·编辑器·harmonyos·鸿蒙
痕忆丶3 小时前
OpenHarmony北向开发基础之 沙箱机制+分布式文件
harmonyos
logomister设计公司阿燕4 小时前
华为商标“减法哲学”:极简主义如何成就全球品牌?
python·华为
youtootech14 小时前
HarmonyOS《柚兔学伴》项目实战25-我的页面、Web 嵌入与项目总结
前端·华为·harmonyos