HarmonyOS 6效果实现:边框半透明色值处理,#4D 十六进制 ARGB 解析,低透明度装饰边框实现方法
本文基于 HarmonyOS ArkTS 声明式 UI(ArkUI)开发的爱奇艺风格视频 App 源码,从色彩系统、架构设计、数据模型、五大 Tab、搜索分类、交互动画、内容生态、推荐系统、分类复用、用户系统、流媒体文化、深色观影体验等多个维度进行深度解析,并在文末附录提供完整可运行的 ArkTS 源码。
引言:流媒体时代的深色观影美学
在移动互联网深度渗透的今天,视频流媒体已经成为人们日常娱乐消费的核心场景。从通勤路上的碎片化观看到周末夜晚的沉浸式追剧,视频 App 承载了亿万用户的娱乐需求。爱奇艺、腾讯视频、优酷、芒果 TV、Bilibili 等主流视频平台各有特色,但在视觉设计上逐渐形成了一套共识:深色背景降低视觉疲劳、高饱和主色强化品牌识别、卡片化布局提升内容浏览效率、Tab 导航简化内容分区。
本文解析的这款爱奇艺风格视频 App,正是基于这一设计共识打造的概念应用。它以深黑 #121212 为底色,以爱奇艺标志性的橙色 #FF6A00 为主强调色,构建了一个简洁而高效的移动端视频浏览体验。5 个 Tab(首页/电影/电视剧/综艺/我的)覆盖了主流视频平台的核心内容分区,12 部涵盖电影、电视剧、综艺的精选内容展示了视频内容的多样性,横向滚动推荐、分类筛选 chips、双列宫格等经典布局元素还原了真实视频 App 的浏览体验。
与真实视频 App 不同的是,这款概念应用采用纯 ArkTS 单文件开发,约 500 行代码,无第三方依赖,复制即可在 DevEco Studio 中运行。它不仅是一个视频 App 的视觉复刻,更是 ArkTS 声明式 UI 开发的完整教学案例,涵盖组件化架构、@Link/@Prop 状态传递、@Builder 自定义构建函数、ForEach 列表渲染、Scroll 横向/纵向滚动、Flex 双列布局、TextInput 搜索框、属性动画、纯函数工具、分类筛选逻辑等核心技术点。
本文将带你深入这款视频 App 的设计与实现,理解深色观影体验背后的设计哲学,以及 ArkTS 声明式 UI 如何高效构建流媒体应用。

色彩风格:深黑底色与橙色活力的碰撞
这款视频 App 的色彩系统是典型的流媒体深色模式设计。深黑 #121212 作为背景色,几乎接近纯黑但保留了极微弱的灰度,避免了纯黑在 OLED 屏幕上可能产生的过强对比,同时最大程度降低了夜间观影时的视觉刺激。卡片色 #1E1E1E 和浅卡片色 #2A2A2A 构建了清晰的视觉层级,让内容卡片在深色背景上自然浮现。
主强调色 #FF6A00 是爱奇艺品牌橙色的精确还原,这是一种高饱和、高明度的暖橙色,在深色背景上具有极强的视觉穿透力。它被用于 Tab 选中态、评分数字、热门标签、搜索框聚焦态等关键交互元素,成为整个 App 的视觉锚点。主色深色 #CC5500 作为橙色的暗色变体,用于需要降低视觉强度的场景。
文字色采用纯白 #FFFFFF 作为主文字色,确保在深色背景上的最大可读性;次级文字色 #AAAAAA 是一种中灰色,用于描述、年份、标签等次要信息,与主文字形成清晰的层级对比。chip 背景色 #333333 和边框色 #444444 构建了筛选标签和分割线的视觉语言。
这套色彩系统的核心设计哲学是:深色背景创造沉浸观影氛围,橙色主色注入平台活力与品牌识别,灰度层级确保信息可读性与视觉舒适度。三者协同,构建了一个既适合长时间观影、又具有鲜明品牌个性的视觉体验。

第一章 项目概述
1.1 项目定位
爱奇艺风格视频 App 是一款基于 HarmonyOS ArkTS 声明式 UI(ArkUI)开发的流媒体概念应用,API 9+,Stage 模型,单文件 .ets,约 500 行代码,严格遵循 ArkTS 严格模式规范(无 Object.assign,无展开运算符,显式类型标注)。应用以爱奇艺为设计参考,还原了主流视频平台的核心浏览体验:首页推荐、内容分类、搜索筛选、个人中心。
与功能完整的真实视频 App 不同,这款概念应用聚焦于前端 UI 层的设计与实现,所有视频数据为静态 mock 数据,视频播放、用户登录、评论互动等后端功能未实现。这种"UI 优先"的定位使得应用代码简洁清晰,适合作为 ArkUI 开发的学习案例和视频 App 的设计参考。
1.2 核心功能
应用的核心功能围绕视频内容浏览展开:
- 首页推荐:横向滚动展示 3 部热门影片(FEATURED),分类筛选 chips(全部/电影/电视剧/综艺),双列宫格展示 12 部影片
- 内容分类:电影、电视剧、综艺三个分类 Tab,每个分类页展示对应类别的影片列表
- 搜索功能:顶部搜索框支持实时输入,搜索文本通过 @Link 在父组件和子组件间双向绑定
- 我的页面:未登录状态展示,菜单列表包含我的收藏、观看历史、我的评论、设置四个入口
- 交互动画:Tab 切换时图标缩放动画(scale 1.2,200ms EaseOut),提升交互反馈感
1.3 技术栈
- 开发框架:HarmonyOS ArkTS 声明式 UI(ArkUI)
- API 版本:API 9+(Stage 模型)
- 开发语言:ArkTS(TypeScript 超集,严格模式)
- 开发工具:DevEco Studio
- 代码规模:约 500 行,单文件 .ets
- 第三方依赖:无
1.4 设计亮点
- 爱奇艺橙色品牌色:#FF6A00 精确还原爱奇艺品牌橙色,在深色背景上具有极强的视觉识别度
- 组件化架构:VideoHeader(搜索头部)、HomeContent(首页)、CategoryContent(分类页,@Prop 复用)、MineContent(我的)四大组件,职责清晰
- @Link 状态传递:搜索文本通过 @Link 在 VideoApp 父组件和 VideoHeader 子组件间双向绑定,可选回调 onSearchChange 增强灵活性
- 分类页组件复用:CategoryContent 通过 @Prop category 接收分类参数,电影/电视剧/综艺三个 Tab 共用同一组件,代码复用率高
- 横向+纵向双滚动:首页横向 Scroll 展示热门推荐,纵向 Scroll 展示分类筛选和双列宫格,两种滚动方向协同
- 双列宫格布局:Flex wrap + SpaceBetween 实现响应式双列宫格,每个卡片包含封面 emoji、标题、评分、年份/热门标签
- ArkTS 严格模式合规:无 Object.assign,无展开运算符(…),所有变量和函数参数显式类型标注,类型安全无运行时错误
第二章 色彩系统
2.1 调色板接口与常量
应用的色彩系统通过 VideoPalette 接口定义,包含 10 个色彩字段,然后通过 SP 常量实例化。这种接口+常量的模式确保了色彩字段的类型安全,同时方便全局引用。
interface VideoPalette {
bg: string;
primary: string;
primaryDark: string;
card: string;
cardLight: string;
text: string;
textSub: string;
chip: string;
border: string;
white: string;
}
const SP: VideoPalette = {
bg: '#121212',
primary: '#FF6A00',
primaryDark: '#CC5500',
card: '#1E1E1E',
cardLight: '#2A2A2A',
text: '#FFFFFF',
textSub: '#AAAAAA',
chip: '#333333',
border: '#444444',
white: '#FFFFFF'
};
2.2 背景色层级
应用的背景色采用三级深灰层级,构建清晰的视觉深度:
- SP.bg #121212:页面最底层背景色,接近纯黑,营造沉浸式观影氛围
- SP.card #1E1E1E:卡片背景色,比背景色亮约 10%,让内容卡片在背景上自然浮现
- SP.cardLight #2A2A2A:浅卡片背景色,比卡片色再亮约 8%,用于视频封面占位、搜索框背景、头像背景等需要更高对比度的区域
这种三级背景色层级是深色模式设计的经典手法。通过微妙的亮度差异,在不使用边框的情况下也能清晰区分不同的视觉区域,同时保持整体色调的统一和沉浸感。
2.3 主强调色系统
主强调色是整个 App 的视觉灵魂,应用采用橙色双色调系统:
- SP.primary #FF6A00:主橙色,爱奇艺品牌色的精确还原。这是一种高饱和(100%)、高明度(约 52%)的暖橙色,在深色背景上具有极强的视觉穿透力。用于 Tab 选中态文字、评分数字、热门标签背景、搜索框占位、登录提示等关键交互元素
- SP.primaryDark #CC5500:暗橙色,主色的暗色变体(亮度降低约 20%),用于需要降低视觉强度的场景,如按钮按下态、渐变过渡等
橙色在视频 App 中的设计意义深远:橙色是暖色调中最具活力和能量的颜色,能够激发用户的观看欲望和情绪共鸣;同时橙色在深色背景上的对比度极高,确保了交互元素的可发现性。爱奇艺选择橙色作为品牌色,正是看中了橙色的"活力感"和"娱乐感",与视频平台的娱乐属性高度契合。
2.4 文字色系统
文字色采用白灰双色调,确保在深色背景上的可读性:
- SP.text #FFFFFF:纯白主文字色,用于标题、按钮文字、评分数字等核心信息,在深色背景上对比度最高,可读性最强
- SP.textSub #AAAAAA:中灰次级文字色,约 67% 亮度的灰色,用于描述文字、年份、标签、未选中 Tab 文字等次要信息。与纯白主文字形成清晰的层级对比,同时避免了纯黑背景上纯白文字可能产生的眩光感
2.5 功能色
- SP.chip #333333:筛选标签背景色,深灰色,用于分类 chips、热门标签等,与橙色选中态形成对比
- SP.border #444444:边框色,比 chip 色稍亮,用于 Tab 栏顶部分割线、头部底部分割线、菜单项底部分割线等
- SP.white #FFFFFF:纯白,与 SP.text 相同,用于需要明确标识为"白色"的场景
2.6 色彩应用原则
应用在色彩使用上遵循以下原则:
- 橙色克制使用:主橙色 #FF6A00 仅用于关键交互元素(选中态、评分、热门标签),不滥用,确保橙色的视觉权重和品牌识别度
- 灰度构建层级:背景、卡片、浅卡片三级灰度构建视觉深度,文字白灰两级构建信息层级,整体色调统一
- 对比度优先:所有文字与背景的对比度均满足 WCAG AA 级标准(4.5:1 以上),确保在各种屏幕亮度下的可读性
- 深色沉浸优先:整体色调偏暗,最大化降低夜间观影时的视觉刺激,营造沉浸式观影氛围
这套色彩系统是流媒体深色模式设计的典范,既满足了功能性需求(可读性、可发现性、层级清晰),又传递了品牌个性(橙色活力、娱乐感)和场景氛围(深色沉浸、观影体验)。
第三章 架构设计
3.1 整体架构
应用采用经典的 ArkUI 声明式组件化架构,整体结构为"根入口组件 + 头部组件 + 内容区组件 + 底部 Tab 栏"的四层结构。根入口组件 VideoApp 管理全局状态(当前 Tab、搜索文本),通过条件渲染切换不同的内容区组件,底部 Tab 栏负责导航切换。
VideoApp(根入口,@Entry)
├── VideoHeader(头部搜索组件,@Link searchText)
├── Stack(内容区,条件渲染)
│ ├── HomeContent(首页)
│ ├── CategoryContent(电影分类,@Prop category='电影')
│ ├── CategoryContent(电视剧分类,@Prop category='电视剧')
│ ├── CategoryContent(综艺分类,@Prop category='综艺')
│ └── MineContent(我的)
└── Row(底部 Tab 栏,6 个 tabItem)
3.2 根入口组件 VideoApp
VideoApp 是应用的根入口组件,使用 @Entry 和 @Component 装饰器标记。它管理两个全局状态:
@State activeTab: number:当前选中的 Tab,使用VideoTab枚举值,默认为VideoTab.HOME(首页)@State searchText: string:搜索框文本,默认为空字符串
根组件的 build() 方法构建了 Column 垂直布局,包含三个部分:
VideoHeader头部组件,通过@Link双向绑定搜索文本,通过可选回调onSearchChange监听搜索变化Stack内容区,通过if/else if条件渲染根据activeTab切换不同的内容组件Row底部 Tab 栏,包含 5 个 Tab 项,通过@Builder tabItem自定义构建函数生成
3.3 头部组件 VideoHeader
VideoHeader 是独立的头部搜索组件,使用 @Component 装饰器标记。它的设计亮点在于状态传递机制:
@Link searchText: string:搜索文本与父组件双向绑定,父组件的searchText变化会同步到子组件,子组件的输入也会同步到父组件onSearchChange?: (v: string) => void:可选的搜索变化回调函数,使用?:标记为可选。在TextInput.onChange中,先更新@Link的searchText,再通过if (this.onSearchChange)判断回调是否存在,存在则调用
这种 @Link + 可选回调 的模式是 ArkUI 中父子组件通信的经典手法。@Link 确保了状态的双向同步,可选回调则提供了额外的事件通知机制,让父组件可以在搜索文本变化时执行额外的逻辑(如触发搜索请求、更新搜索建议等)。
头部组件的布局为 Row 水平排列:左侧是"🎬 影音"品牌标识(橙色加粗),中间用 Column().layoutWeight(1) 撑开空间,右侧是搜索框(圆角胶囊形,包含搜索图标和 TextInput)。
3.4 内容区组件
内容区包含四类组件:
- HomeContent(首页):独立组件,管理视频列表和分类筛选状态。包含横向滚动推荐、分类筛选 chips、双列宫格三个核心区域
- CategoryContent(分类页):可复用组件,通过
@Prop category: string接收分类参数,在初始化时通过VIDEOS.filter(v => v.category === this.category)过滤出对应类别的视频列表。电影、电视剧、综艺三个 Tab 共用此组件 - MineContent(我的):独立组件,展示未登录状态和菜单列表。通过
@Builder menuItem自定义构建函数生成菜单项
3.5 底部 Tab 栏
底部 Tab 栏通过 @Builder tabItem(icon, label, tab) 自定义构建函数生成每个 Tab 项。每个 Tab 项包含:
- 图标 Text(emoji,选中时 scale 1.2 缩放动画)
- 标签 Text(选中时橙色,未选中时灰色)
- 点击事件切换
activeTab
Tab 栏使用 Row 水平布局,每个 Tab 项通过 .layoutWeight(1) 平均分配宽度。背景色为 SP.card,顶部有 0.5px 的边框线与内容区分隔。
3.6 组件通信机制
应用的组件通信采用多种机制协同:
- @State(组件内状态):
VideoApp的activeTab和searchText,HomeContent的videos和selectedCategory,CategoryContent的videos,均为组件内部状态 - @Link(父子双向绑定):
VideoHeader的searchText通过@Link与VideoApp的searchText双向绑定 - @Prop(父子单向传参):
CategoryContent的category通过@Prop从父组件接收分类名称,单向传递,子组件不修改 - 可选回调函数:
VideoHeader的onSearchChange可选回调,父组件传入回调函数,子组件在搜索变化时调用 - @Builder 自定义构建函数:
tabItem、menuItem等通过@Builder封装可复用的 UI 片段
这种多机制协同的组件通信设计,既保证了状态管理的清晰性,又提供了足够的灵活性,是 ArkUI 组件化开发的最佳实践。
第四章 数据模型
4.1 接口定义
应用定义了两个核心接口,分别描述视频内容和分类标签:
interface VideoItem {
id: number;
title: string;
cover: string; // emoji 占位
category: string; // '电影' | '电视剧' | '综艺'
score: number; // 评分 0-10
year: string;
desc: string;
hot: boolean;
}
interface CategoryTab {
name: string;
key: string;
}
VideoItem 接口描述了一部视频内容的完整信息,包含 8 个字段:
id:唯一标识,数字类型title:视频标题,字符串类型cover:封面图,使用 emoji 占位(如 🚀、🚓、🎤),概念应用中不使用真实图片category:视频分类,字符串类型,取值为’电影’、‘电视剧’、'综艺’三种score:评分,数字类型,0-10 分制,保留一位小数year:上映年份,字符串类型desc:视频简介,字符串类型hot:是否热门,布尔类型,用于筛选 FEATURED 推荐列表和显示热门标签
CategoryTab 接口描述了一个分类筛选标签,包含 2 个字段:
name:分类显示名称,如"全部"、“电影”、“电视剧”、“综艺”key:分类键值,如"all"、“movie”、“tv”、“variety”,用于逻辑判断
4.2 静态数据
应用定义了三组静态数据常量:
CATEGORIES(分类列表):包含 4 个分类标签,用于首页的分类筛选 chips:
const CATEGORIES: CategoryTab[] = [
{ name: '全部', key: 'all' },
{ name: '电影', key: 'movie' },
{ name: '电视剧', key: 'tv' },
{ name: '综艺', key: 'variety' }
];
VIDEOS(视频列表):包含 12 部视频,覆盖电影、电视剧、综艺三个分类,是应用的核心数据:
const VIDEOS: VideoItem[] = [
{ id: 1, title: '流浪地球2', cover: '🚀', category: '电影', score: 8.7, year: '2023', desc: '太阳即将毁灭,人类启动流浪地球计划。', hot: true },
{ id: 2, title: '狂飙', cover: '🚓', category: '电视剧', score: 9.2, year: '2023', desc: '正义与邪恶的较量,跨越二十年的追捕。', hot: true },
// ... 共12部
];
12 部视频的分类分布为:电影 5 部(流浪地球2、满江红、长津湖之水门桥、独行月球)、电视剧 4 部(狂飙、三体、梦华录、警察荣誉)、综艺 3 部(声生不息、向往的生活、披荆斩棘、乘风破浪——实际是4部,让我重新数)。
实际上从代码看:
- 电影:流浪地球2(1)、满江红(4)、长津湖之水门桥(7)、独行月球(10) = 4部
- 电视剧:狂飙(2)、三体(5)、梦华录(8)、警察荣誉(11) = 4部
- 综艺:声生不息(3)、向往的生活(6)、披荆斩棘(9)、乘风破浪(12) = 4部
正好每个分类 4 部,共 12 部,分布均匀。
FEATURED(热门推荐):从 VIDEOS 中过滤出 hot: true 的视频,取前 3 部,用于首页横向滚动推荐:
const FEATURED: VideoItem[] = VIDEOS.filter(v => v.hot).slice(0, 3);
热门视频为:流浪地球2(电影,8.7分)、狂飙(电视剧,9.2分)、三体(电视剧,8.9分),都是 2023 年的高口碑热门作品。
4.3 数据设计特点
- 分类均衡:12 部视频平均分布在电影、电视剧、综艺三个分类,每个分类 4 部,确保分类页内容充实
- 热门精选:3 部热门视频(流浪地球2、狂飙、三体)都是 2023 年的现象级作品,具有广泛的认知度和口碑
- 评分真实:评分数据参考了豆瓣等平台的真实评分,如狂飙 9.2 分、流浪地球2 8.7 分、三体 8.9 分,增强了数据的真实感
- emoji 封面:使用 emoji 作为封面占位,避免了外部图片依赖,同时 emoji 的视觉辨识度高,在概念应用中足以传达视频内容的主题
- 数据派生:FEATURED 不是独立定义的,而是从 VIDEOS 中通过 filter + slice 派生而来,确保了数据的一致性和可维护性
4.4 纯函数工具
应用定义了两个纯函数工具:
function genId(): number {
return Math.floor(Math.random() * 1000000);
}
function formatScore(s: number): string {
return s.toFixed(1);
}
genId():生成随机 ID,返回 0-999999 之间的随机整数。用于新增数据时生成唯一标识。虽然当前应用的静态数据已预定义 ID,但此函数为后续扩展(如用户添加收藏、观看历史等)提供了基础工具。
formatScore(s):格式化评分,将数字评分格式化为保留一位小数的字符串。如 8.7 → “8.7”,9 → “9.0”。使用 toFixed(1) 确保评分显示的一致性,在视频卡片中统一显示为"⭐ 8.7"的格式。
这两个纯函数都是无副作用的工具函数,输入确定则输出确定,符合函数式编程的最佳实践,也便于单元测试和复用。
第五章 五大 Tab 详解
应用的底部导航包含 5 个 Tab:首页、电影、电视剧、综艺、我的。这 5 个 Tab 覆盖了主流视频平台的核心内容分区和用户中心,是视频 App 的标准导航结构。
5.1 首页:推荐与发现
首页是应用的默认 Tab,也是内容最丰富的页面。它包含三个核心区域:
横向滚动推荐区:使用 Scroll 组件的 ScrollDirection.Horizontal 横向滚动,展示 FEATURED 数组中的 3 部热门视频。每个推荐卡片宽度 140px,包含大尺寸封面 emoji(60px,120px 高)、标题(14px 加粗)、热门标签(橙色背景)。横向滚动是视频 App 首页的经典设计,用于展示编辑精选或热门推荐,用户可以快速浏览多部影片。
分类筛选区:使用 Row + ForEach 渲染 CATEGORIES 数组中的 4 个分类标签(全部/电影/电视剧/综艺)。选中的标签显示橙色文字和橙色半透明背景(SP.primary + '33',即 20% 透明度的橙色),未选中的标签显示灰色文字和透明背景。点击标签时,通过 selectedCategory 状态切换,并根据分类键值过滤 videos 数组:
- “all”:显示全部 12 部视频
- “movie”:过滤 category === ‘电影’
- “tv”:过滤 category === ‘电视剧’
- “variety”:过滤 category === ‘综艺’
分类键值到中文分类名的映射使用 Record<string, string> 对象,这是 ArkTS 严格模式下的类型安全写法。
双列宫格区:使用 Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) 实现响应式双列宫格,每个卡片宽度 47%,自动换行。每个视频卡片包含:封面 emoji(44px,90px 高)、标题(12px,单行省略)、评分行(⭐ 评分 + 热门标签 🔥)。双列宫格是视频 App 内容列表的经典布局,在有限的屏幕空间内展示尽可能多的内容,同时保持卡片的可读性。

5.2 电影:分类内容页
电影 Tab 使用 CategoryContent 组件,传入 category: '电影' 参数。组件初始化时通过 VIDEOS.filter(v => v.category === this.category) 过滤出 4 部电影:流浪地球2、满江红、长津湖之水门桥、独行月球。
页面结构为:顶部标题"🔥 热门 电影"(16px 加粗),下方双列宫格展示电影列表。每个电影卡片包含:封面 emoji(48px,100px 高)、标题(13px,单行省略)、评分行(⭐ 评分 + 年份)。
与首页的双列宫格相比,分类页的卡片尺寸稍大(封面 48px vs 44px,标题 13px vs 12px),因为分类页内容较少(4 部 vs 12 部),可以适当放大卡片尺寸提升视觉舒适度。评分行显示年份而非热门标签,因为分类页的所有内容都是同一类别,热门标签的区分意义不大。

5.3 电视剧:分类内容页
电视剧 Tab 同样使用 CategoryContent 组件,传入 category: '电视剧' 参数。过滤出 4 部电视剧:狂飙、三体、梦华录、警察荣誉。
页面结构与电影 Tab 完全一致,只是内容数据不同。这种组件复用设计是 ArkUI 开发的核心优势之一:通过 @Prop 传入不同的参数,同一组件可以渲染不同的内容,大幅减少了代码重复。
电视剧分类的 4 部作品都是 2022-2023 年的高口碑剧集,涵盖了扫黑(狂飙)、科幻(三体)、古装(梦华录)、现实(警察荣誉)四种题材,展示了电视剧内容的多样性。

5.4 综艺:分类内容页
综艺 Tab 使用 CategoryContent 组件,传入 category: '综艺' 参数。过滤出 4 部综艺:声生不息、向往的生活、披荆斩棘、乘风破浪。
页面结构与电影、电视剧 Tab 完全一致。综艺分类的 4 部作品涵盖了音乐竞演(声生不息、披荆斩棘、乘风破浪)和慢综艺(向往的生活)两种类型,展示了综艺内容的丰富性。
三个分类 Tab 共用同一组件的设计,使得新增一个分类 Tab 只需要在底部 Tab 栏添加一个 tabItem,并在 Stack 中添加一个 CategoryContent 调用即可,无需编写新的组件代码,扩展性极强。

5.5 我的:用户中心
我的 Tab 使用独立的 MineContent 组件,展示用户中心页面。页面包含三个区域:
用户信息卡:展示未登录状态。包含圆形头像占位(80px,emoji 👤)、"未登录"文字(16px 加粗)、"点击登录"提示(12px 橙色)。整个卡片背景为 SP.card,圆角 12px,内边距 20px。未登录状态是视频 App 的常见设计,引导用户登录后解锁个性化推荐、观看历史同步、收藏同步等功能。
菜单列表:包含 4 个菜单项:我的收藏(📺)、观看历史(⏳)、我的评论(💬)、设置(⚙️)。每个菜单项通过 @Builder menuItem(icon, label) 自定义构建函数生成,结构为:图标 + 标签 + 右箭头(>),底部有 0.5px 分割线。菜单列表背景为 SP.card,圆角 12px,左右内边距 12px。
底部标语:“— 影音有你,精彩无限 —”(10px,灰色),作为页面的装饰性结尾。
我的页面是视频 App 用户体系的入口,虽然当前仅展示未登录状态和静态菜单,但为后续扩展用户登录、个人资料、收藏管理、历史记录、设置等功能提供了基础框架。

第六章 搜索与分类筛选
搜索和分类筛选是视频 App 内容发现的两大核心机制。搜索帮助用户精准定位已知内容,分类筛选帮助用户在未知内容中发现感兴趣的影片。这款应用同时实现了这两种机制,展示了视频 App 内容导航的完整设计。
6.1 搜索框组件设计
搜索框位于应用顶部的 VideoHeader 组件中,是一个独立的可复用组件。搜索框的设计有以下几个亮点:
胶囊形外观:搜索框宽度 180px,高度 32px,圆角 16px(高度的一半),形成胶囊形外观。背景色为 SP.cardLight(#2A2A2A),在深色头部背景上自然浮现。胶囊形搜索框是移动端 App 的经典设计,圆润的外观降低了视觉攻击性,同时较大的圆角提供了友好的触控体验。
内部布局:搜索框内部使用 Row 水平布局,左侧是搜索图标 🔍(14px,灰色),右侧是 TextInput 输入框(占满剩余空间,透明背景,无边框)。图标与输入框之间有 4px 间距。这种"图标+输入框"的组合是搜索框的标准设计,图标提供视觉提示,输入框承载文本输入。
占位提示:TextInput 的 placeholder 为"搜索视频",使用默认的灰色占位文字。简洁的占位提示告诉用户搜索框的用途,同时不占用过多视觉空间。
6.2 @Link 双向绑定与可选回调
搜索框的状态管理是这个组件最核心的设计亮点。VideoHeader 组件通过两种机制与父组件 VideoApp 通信:
@Link 双向绑定:@Link searchText: string 将子组件的 searchText 与父组件的 searchText 双向绑定。当用户在 TextInput 中输入文字时,onChange 回调中执行 this.searchText = v,更新子组件的 searchText,由于 @Link 的双向绑定特性,父组件的 searchText 也会同步更新。反之,如果父组件的 searchText 被其他逻辑修改(如点击搜索建议填充),子组件的 TextInput 显示内容也会同步更新。
可选回调 onSearchChange:onSearchChange?: (v: string) => void 是一个可选的回调函数,使用 ?: 标记为可选。在 TextInput.onChange 中,先更新 @Link 的 searchText,然后通过 if (this.onSearchChange) 判断回调是否存在,如果存在则调用 this.onSearchChange(v)。
这种 @Link + 可选回调 的双重机制设计非常巧妙:
@Link确保了搜索文本的状态同步,父组件可以随时获取最新的搜索文本- 可选回调提供了事件通知机制,父组件可以在搜索文本变化时执行额外逻辑(如触发搜索请求、更新搜索建议列表、埋点统计等)
- 回调是可选的,如果父组件不需要监听搜索变化,可以不传入回调,组件依然正常工作
在当前应用中,父组件传入了 onSearchChange: (v: string) => { this.searchText = v; },但这个回调实际上只是重复了 @Link 已经完成的状态同步,属于冗余代码。在真实应用中,这个回调可以用于触发搜索 API 请求、显示搜索建议、记录搜索历史等更有意义的逻辑。
6.3 分类筛选机制
分类筛选位于首页的分类筛选区,使用 4 个 chips 标签实现:全部、电影、电视剧、综艺。
筛选逻辑:点击分类标签时,更新 selectedCategory 状态,并根据分类键值过滤 videos 数组:
if (cat.key === 'all') {
this.videos = VIDEOS;
} else {
const map: Record<string, string> = {
'movie': '电影',
'tv': '电视剧',
'variety': '综艺'
};
this.videos = VIDEOS.filter(v => v.category === map[cat.key]);
}
这里使用 Record<string, string> 对象实现英文键值到中文分类名的映射,是 ArkTS 严格模式下的类型安全写法。Record<K, V> 是 TypeScript 的内置工具类型,表示键为 K 类型、值为 V 类型的对象,确保了映射表的类型安全。
视觉反馈:选中的分类标签显示橙色文字(SP.primary)和橙色半透明背景(SP.primary + '33',即橙色 + 20% 透明度的十六进制后缀 33),圆角 14px,内边距上下 4px 左右 14px。未选中的标签显示灰色文字(SP.textSub)和透明背景。选中态与未选中态的视觉对比清晰,用户可以快速识别当前筛选状态。
默认状态:selectedCategory 默认为 'all',即显示全部视频。这是分类筛选的标准默认行为,用户进入首页时看到最丰富的内容,然后根据需要缩小筛选范围。
6.4 搜索与筛选的协同设计
在真实视频 App 中,搜索和分类筛选通常是协同工作的:用户可以先选择分类,再在该分类下搜索;或者先搜索,再在搜索结果中按分类筛选。当前应用中,搜索和分类筛选是独立的两个机制,搜索文本仅存储在状态中但未实际过滤视频列表,分类筛选独立过滤视频列表。
这种设计是概念应用的简化处理。在真实应用中,可以将搜索和筛选结合:videos = VIDEOS.filter(v => v.category === selectedCategory && v.title.includes(searchText)),实现分类+搜索的组合过滤。这种组合过滤是视频 App 内容发现的高级功能,能够帮助用户在大量内容中精准定位目标影片。

第七章 交互动画与纯函数
虽然这款视频 App 的交互相对简洁,但依然实现了关键的交互动画和纯函数工具,展示了 ArkUI 动画和函数式编程的核心用法。
7.1 Tab 图标缩放动画
应用唯一的显式动画是底部 Tab 切换时的图标缩放动画。在 @Builder tabItem 中,图标 Text 使用了 .scale() 和 .animation() 两个属性:
Text(icon)
.fontSize(22)
.scale({
x: this.activeTab === tab ? 1.2 : 1,
y: this.activeTab === tab ? 1.2 : 1
})
.animation({ duration: 200, curve: Curve.EaseOut })
动画原理:.scale() 属性设置图标的缩放比例,当 Tab 被选中时(this.activeTab === tab),缩放比例为 1.2(放大 20%),未选中时为 1(原始大小)。.animation() 属性设置动画参数,时长 200ms,缓动曲线为 Curve.EaseOut(先快后慢)。
当用户点击一个未选中的 Tab 时,activeTab 状态更新,新选中的 Tab 图标从 scale 1 动画到 scale 1.2,之前选中的 Tab 图标从 scale 1.2 动画到 scale 1。两个动画同时进行,形成"此消彼长"的视觉效果,清晰地反馈了 Tab 切换的交互。
设计意义:Tab 图标缩放动画虽然简单,但在交互体验中扮演着重要角色:
- 视觉反馈:用户点击 Tab 后,图标放大提供了即时的视觉反馈,确认操作已被响应
- 状态指示:放大的图标清晰地标示了当前选中的 Tab,用户可以快速识别当前所在页面
- 流畅感:200ms 的 EaseOut 动画时长适中,既不会太快导致动画不明显,也不会太慢导致操作卡顿,整体感觉流畅自然
- 品牌活力:缩放动画为静态的 Tab 栏注入了动态感,与视频 App 的娱乐属性相契合
7.2 隐式动画机制
ArkUI 的动画系统采用隐式动画机制,即通过 .animation() 属性声明动画参数,当动画属性(如 scale、opacity、width、height 等)发生变化时,系统自动在旧值和新值之间插值生成动画。开发者不需要手动管理动画的开始、结束、帧更新等细节,只需要声明"这个属性变化时需要动画"以及"动画的时长和曲线"即可。
这种隐式动画机制大大简化了动画开发的复杂度。在传统的命令式 UI 框架中,实现一个缩放动画需要手动创建动画对象、设置动画属性、添加动画更新监听器、在更新回调中手动设置缩放值、处理动画结束清理等一系列步骤。而在 ArkUI 中,只需要两行代码:.scale({...}) 设置目标值,.animation({...}) 声明动画参数,系统自动完成剩余的所有工作。
7.3 纯函数工具
应用定义了两个纯函数工具:genId() 和 formatScore(s)。
genId():
function genId(): number {
return Math.floor(Math.random() * 1000000);
}
生成 0-999999 之间的随机整数,用于新增数据时生成唯一标识。虽然当前应用的静态数据已预定义 ID,但此函数为后续扩展提供了基础工具。在真实应用中,唯一 ID 通常由后端生成,但在前端本地状态管理中(如本地收藏、本地历史记录),前端生成临时 ID 是常见的做法。
formatScore(s):
function formatScore(s: number): string {
return s.toFixed(1);
}
将数字评分格式化为保留一位小数的字符串。toFixed(1) 是 JavaScript/TypeScript 的内置方法,将数字四舍五入到指定小数位数并返回字符串。如 8.7.toFixed(1) → "8.7",9.toFixed(1) → "9.0",8.65.toFixed(1) → "8.7"(四舍五入)。
这个函数确保了所有视频卡片的评分显示格式统一,都是"X.X"的一位小数格式,避免了 8.7 和 9 显示不一致的问题。在视频 App 中,评分是用户选择影片的重要参考,统一的评分显示格式提升了信息的可读性和专业感。
7.4 纯函数的设计价值
纯函数是函数式编程的核心概念,具有以下特点:
- 无副作用:函数执行不修改任何外部状态,不依赖任何外部可变状态
- 确定性:相同的输入总是产生相同的输出
- 可测试性:由于无副作用和确定性,纯函数非常容易单元测试
- 可复用性:纯函数不依赖特定的上下文,可以在任何地方调用
genId() 虽然使用了 Math.random(),严格来说不是纯函数(因为输出不确定),但它是无副作用的工具函数,不修改任何外部状态。formatScore(s) 是严格意义上的纯函数,相同的数字输入总是产生相同的字符串输出。
在 ArkTS 严格模式下,纯函数工具的使用尤为重要。严格模式限制了动态特性(如 Object.assign、展开运算符等),鼓励使用不可变数据和纯函数。将通用的逻辑封装为纯函数工具,既提高了代码的可维护性,也符合严格模式的编程范式。
7.5 动画与性能
虽然这款应用的动画很简单,但 ArkUI 的动画系统在性能上做了大量优化。ArkUI 的动画运行在独立的渲染线程上,不会阻塞 UI 线程,即使在复杂的页面布局中也能保持 60fps 的流畅帧率。同时,ArkUI 的动画系统支持硬件加速,缩放、平移、旋转等变换动画直接由 GPU 合成,性能优异。
对于视频 App 来说,动画性能尤为重要。视频 App 的页面通常包含大量图片和视频缩略图,内存占用较高,如果动画性能不佳,容易出现卡顿和掉帧。ArkUI 的隐式动画机制和硬件加速确保了即使在内容丰富的页面中,Tab 切换、卡片点击等交互动画也能流畅运行,为用户提供丝滑的交互体验。
第八章 视频内容生态
8.1 内容分类体系
应用的视频内容采用三级分类体系:大类(电影/电视剧/综艺)→ 题材(科幻/犯罪/音乐/古装等)→ 单部影片。虽然应用中只显式实现了大类分类(通过 category 字段),但从 12 部影片的题材分布可以看出完整的内容生态设计。
电影类(4部):
- 流浪地球2:科幻题材,2023年,8.7分,热门
- 满江红:历史悬疑题材,2023年,8.2分
- 长津湖之水门桥:战争题材,2022年,8.4分
- 独行月球:科幻喜剧题材,2022年,7.9分
电视剧类(4部):
- 狂飙:犯罪题材,2023年,9.2分,热门
- 三体:科幻题材,2023年,8.9分,热门
- 梦华录:古装题材,2022年,8.6分
- 警察荣誉:现实题材,2022年,8.7分
综艺类(4部):
- 声生不息:音乐竞演,2022年,8.5分
- 向往的生活:慢综艺/生活,2023年,7.8分
- 披荆斩棘:音乐竞演,2023年,8.1分
- 乘风破浪:音乐竞演,2022年,8.3分
这种题材分布展示了视频内容的多样性:电影涵盖科幻、历史、战争、喜剧;电视剧涵盖犯罪、科幻、古装、现实;综艺涵盖音乐竞演和慢综艺两大类型。用户无论偏好哪种题材,都能在应用中找到感兴趣的内容。
8.2 评分体系与内容质量
应用采用 10 分制评分体系,所有影片的评分都在 7.8-9.2 分之间,属于中高口碑区间。这种评分设计有以下考虑:
- 筛选优质内容:概念应用只展示高口碑内容,避免低质量内容影响用户体验
- 评分差异化:评分从 7.8 到 9.2 有 1.4 分的区间,不同影片的评分有明显差异,用户可以根据评分快速判断内容质量
- 头部内容突出:狂飙 9.2 分是全场最高分,三体 8.9 分次之,流浪地球2 8.7 分第三,这三部热门影片的评分都在 8.7 分以上,形成了清晰的头部内容梯队
- 评分真实感:评分数据参考了豆瓣等平台的真实评分,如狂飙 9.2 分、流浪地球2 8.7 分、三体 8.9 分,增强了数据的真实感和可信度
评分在视频卡片中以"⭐ 8.7"的格式显示,橙色文字,是卡片中最醒目的信息之一。评分是用户选择影片的重要参考,高评分影片更容易吸引用户点击观看。
8.3 热门内容与推荐逻辑
应用的热门内容通过 hot: boolean 字段标记,共有 3 部热门影片:流浪地球2、狂飙、三体。这 3 部影片通过 FEATURED 常量派生,用于首页横向滚动推荐区。
热门内容的筛选逻辑是:VIDEOS.filter(v => v.hot).slice(0, 3),即从所有视频中过滤出 hot: true 的影片,取前 3 部。这种逻辑简单清晰,热门标记由内容运营人员手动设置,可以根据时效性动态调整(如新片上映时标记为热门,下映后取消热门标记)。
在真实视频 App 中,热门推荐的逻辑要复杂得多,通常综合考虑以下因素:
- 播放量/观看次数
- 完播率/观看时长
- 用户评分/点赞数
- 评论数/分享数
- 上新时间/时效性
- 编辑推荐/运营加权
- 用户个性化偏好(协同过滤、内容推荐算法)
概念应用简化了推荐逻辑,仅使用手动标记的 hot 字段,但这种简化为理解视频推荐系统的基本原理提供了清晰的入口。
8.4 内容时效性
应用的 12 部影片上映年份集中在 2022-2023 年,其中 2023 年 6 部,2022 年 6 部。这种时效性设计有以下考虑:
- 贴近当下:2022-2023 年的影片是用户近期观看和讨论的内容,具有较高的认知度和话题性
- 新老结合:2023 年的新片(流浪地球2、狂飙、三体等)和 2022 年的经典片(长津湖之水门桥、梦华录等)结合,既有时效性又有内容沉淀
- 年份显示:分类页的视频卡片显示年份信息,用户可以快速判断影片的时效性
- 持续更新:在真实应用中,内容库会持续更新,新片上映时及时添加,老片根据观看数据决定是否保留
内容时效性是视频 App 的核心竞争力之一。用户使用视频 App 的主要动机之一是观看最新的影视内容,因此视频平台需要持续采购和更新内容库,保持内容的新鲜度。同时,经典老片也是内容库的重要组成部分,满足用户的怀旧和深度观看需求。
第九章 首页推荐系统
9.1 首页布局结构
首页是视频 App 的核心页面,也是用户打开应用后看到的第一个页面。首页的设计目标是在有限的屏幕空间内,尽可能高效地展示内容,引导用户发现和观看感兴趣的影片。应用的首页采用三段式布局:
第一段:横向滚动推荐区(高度约 190px)
- 展示 3 部热门影片(FEATURED)
- 横向 Scroll 滚动,用户可以左右滑动浏览
- 每个卡片宽度 140px,包含大封面、标题、热门标签
- 位于页面最顶部,视觉权重最高
第二段:分类筛选区(高度约 40px)
- 展示 4 个分类 chips:全部、电影、电视剧、综艺
- 水平排列,可横向滚动(如果 chips 数量超出屏幕宽度)
- 选中态橙色高亮,未选中态灰色
- 位于推荐区下方,宫格区上方
第三段:双列宫格区(占据剩余空间,可纵向滚动)
- 展示当前分类下的所有影片(全部 12 部,或筛选后的 4 部)
- 双列 Flex wrap 布局,每个卡片宽度 47%
- 每个卡片包含封面、标题、评分、热门标签
- 可纵向滚动浏览更多内容
这种三段式布局是视频 App 首页的经典设计:顶部横向推荐展示编辑精选,中间分类筛选提供内容导航,底部双列宫格展示完整内容列表。三个区域各司其职,协同完成内容发现的任务。
9.2 横向滚动推荐设计
横向滚动推荐区是首页最醒目的区域,用于展示编辑精选或热门推荐。设计要点:
卡片设计:每个推荐卡片宽度 140px,背景色 SP.card,圆角 12px,内边距 10px。卡片包含三个元素:
- 封面 emoji:fontSize 60px,textAlign Center,宽度 100%,高度 120px,背景色
SP.cardLight,圆角 8px - 标题:fontSize 14px,fontWeight Bold,fontColor
SP.text,margin top 6px - 热门标签:"🔥 热门"文字,fontSize 10px,fontColor
SP.primary,背景色SP.primary + '33'(20%透明度橙色),圆角 4px,内边距上下 2px 左右 6px,margin top 4px
大尺寸封面(120px 高)是推荐卡片的核心视觉元素,占据了卡片约 70% 的高度,确保推荐内容的视觉冲击力。标题和热门标签作为辅助信息,提供内容的基本描述和热度标识。
滚动设计:使用 Scroll 组件的 ScrollDirection.Horizontal 实现横向滚动。Scroll 内部是一个 Row,包含 3 个推荐卡片,卡片之间有 10px 的左右 margin。Row 的左右 padding 各 8px,确保第一个和最后一个卡片与屏幕边缘有适当间距。
横向滚动是移动端展示多卡片内容的经典方案。相比纵向滚动,横向滚动在同一屏内可以展示更多卡片的预览(通常 2-3 个卡片可见),用户可以通过左右滑动快速浏览,同时不会占用过多的纵向空间。
交互设计:每个推荐卡片有 .onClick(() => {}) 点击事件,但当前为空实现。在真实应用中,点击推荐卡片会跳转到视频详情页或直接开始播放。空实现是概念应用的简化处理,保留了交互入口但未实现具体逻辑。
9.3 分类筛选与宫格联动
分类筛选区和双列宫格区通过 selectedCategory 状态联动。当用户点击分类 chips 时,selectedCategory 更新,同时 videos 数组根据分类过滤,宫格区重新渲染过滤后的视频列表。
联动逻辑:
- 用户点击"电影"chip →
selectedCategory = 'movie' - 执行过滤:
videos = VIDEOS.filter(v => v.category === '电影') - 宫格区重新渲染,只显示 4 部电影
- "电影"chip 显示橙色选中态,其他 chip 显示灰色未选中态
全部状态:当用户点击"全部"chip 时,videos = VIDEOS,恢复显示全部 12 部影片。"全部"是默认选中状态,用户进入首页时看到最丰富的内容。
视觉过渡:由于 ArkUI 的声明式渲染机制,videos 数组变化时,宫格区会自动重新渲染。ForEach 会根据新的数组长度添加或移除卡片。当前应用没有添加显式的过渡动画,卡片会直接切换。在真实应用中,可以添加淡入淡出、滑动等过渡动画,提升切换时的视觉流畅度。
9.4 双列宫格设计
双列宫格是首页内容列表的核心布局,使用 Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) 实现。
布局原理:
FlexWrap.Wrap:内容超出容器宽度时自动换行FlexAlign.SpaceBetween:每行的第一个卡片靠左对齐,最后一个卡片靠右对齐,中间卡片均匀分布- 每个卡片宽度 47%,两个卡片之间有 6% 的间距(100% - 47% × 2 = 6%)
- 卡片之间的纵向间距通过
margin top 8px实现
这种双列布局是响应式的,在不同屏幕宽度下都能保持两列布局,卡片宽度会根据屏幕宽度自动调整。47% 的宽度设计确保了两个卡片之间有适当的间距,同时卡片本身有足够的宽度展示内容。
卡片设计:每个视频卡片背景色 SP.card,圆角 10px,内边距 8px,margin top 8px。卡片包含三个元素:
- 封面 emoji:fontSize 44px,textAlign Center,宽度 100%,高度 90px,背景色
SP.cardLight,圆角 6px - 标题:fontSize 12px,fontWeight Medium,fontColor
SP.text,maxLines 1,textOverflow Ellipsis,宽度 100%,margin top 4px - 评分行:Row 水平布局,左侧"⭐ 评分"(fontSize 10px,fontColor
SP.primary),中间Column().layoutWeight(1)撑开空间,右侧热门标签 🔥(如果v.hot为 true)
与推荐卡片相比,宫格卡片的尺寸更小(封面 90px vs 120px,标题 12px vs 14px),因为宫格需要在有限空间内展示尽可能多的内容。标题使用 maxLines 1 + textOverflow Ellipsis 确保长标题不会破坏布局,超出部分显示省略号。
9.5 推荐系统的设计哲学
首页推荐系统的设计哲学可以概括为:编辑精选 + 分类导航 + 完整列表的三层漏斗。
- 顶层漏斗(横向推荐):编辑精选的 3 部热门影片,视觉权重最高,用户进入首页首先看到。这一层的目标是"强推",将平台最想推荐的内容直接展示给用户,提高头部内容的曝光率
- 中层漏斗(分类筛选):4 个分类标签,用户可以根据偏好缩小内容范围。这一层的目标是"导航",帮助用户在大量内容中快速定位感兴趣的分类
- 底层漏斗(双列宫格):当前分类下的完整内容列表,用户可以纵向滚动浏览所有内容。这一层的目标是"穷尽",确保用户不会错过任何内容,可以自由探索
三层漏斗从"强推"到"导航"到"穷尽",覆盖了用户从"被动接受推荐"到"主动探索内容"的完整行为路径。无论用户是想随便看看(看顶部推荐),还是有明确的分类偏好(用分类筛选),还是想穷尽所有内容(滚动宫格),都能在首页找到合适的内容发现方式。
第十章 分类页复用设计
10.1 CategoryContent 组件设计
分类页是应用中组件复用设计的典范。电影、电视剧、综艺三个 Tab 共用同一个 CategoryContent 组件,通过 @Prop category: string 传入不同的分类参数,渲染不同的内容。
@Component struct CategoryContent {
@Prop category: string;
@State videos: VideoItem[] = VIDEOS.filter(v => v.category === this.category);
build() {
// ... 渲染分类标题和视频宫格
}
}
@Prop 装饰器:@Prop category: string 声明了一个单向传入的属性。父组件通过 CategoryContent({ category: '电影' }) 传入分类名称,子组件接收后只读使用,不修改。@Prop 与 @Link 的区别在于:@Prop 是单向的(父→子),子组件修改不会同步到父组件;@Link 是双向的(父↔子),子组件修改会同步到父组件。分类参数只需要从父组件传入,子组件不需要修改,因此使用 @Prop 是合适的选择。
初始化过滤:@State videos: VideoItem[] = VIDEOS.filter(v => v.category === this.category) 在组件初始化时,根据传入的 category 参数过滤出对应类别的视频列表。这个过滤只在组件初始化时执行一次,之后 videos 状态由组件内部管理。
10.2 分类页布局结构
分类页的布局结构比首页简单,只包含两个区域:
标题区:Row 水平布局,左侧是"🔥 热门 {category}“标题(fontSize 16px,fontWeight Bold,fontColor SP.text),margin left 12px top 12px。标题动态显示分类名称,如"🔥 热门 电影”、“🔥 热门 电视剧”、“🔥 热门 综艺”。
双列宫格区:与首页的双列宫格设计基本一致,但卡片尺寸稍大:
- 封面 emoji:fontSize 48px(首页 44px),高度 100px(首页 90px)
- 标题:fontSize 13px(首页 12px)
- 评分行:左侧"⭐ 评分",中间撑开,右侧显示年份
v.year(首页显示热门标签 🔥)
分类页卡片尺寸稍大的原因是:分类页的内容较少(每个分类 4 部,首页全部 12 部),可以适当放大卡片尺寸提升视觉舒适度。同时,分类页的所有内容都是同一类别,热门标签的区分意义不大,因此替换为年份信息,提供更多的内容维度。
10.3 组件复用的价值
CategoryContent 组件的复用设计体现了组件化开发的核心价值:
- 减少代码重复:如果不使用组件复用,需要为电影、电视剧、综艺三个 Tab 分别编写三个内容组件,每个组件的代码几乎完全相同,只是过滤条件不同。使用组件复用后,只需要编写一个
CategoryContent组件,通过参数区分,代码量减少约 60% - 提高可维护性:当需要修改分类页的布局或样式时,只需要修改
CategoryContent一个组件,三个分类页会同时更新。如果是三个独立组件,需要分别修改三处,容易遗漏或不一致 - 增强扩展性:新增一个分类 Tab(如"动漫"、“纪录片”)只需要两步:在底部 Tab 栏添加一个 tabItem,在 Stack 中添加一个
CategoryContent({ category: '动漫' })调用。无需编写新的组件代码,扩展成本极低 - 一致性保障:三个分类页使用同一组件,确保了布局、样式、交互的完全一致性。用户在不同分类页之间切换时,不会感受到布局变化带来的认知负担
10.4 @Prop 与组件生命周期
@Prop 装饰器的属性在组件初始化时从父组件传入,之后与父组件断开连接。这意味着:
- 父组件的
category变化不会同步到已创建的CategoryContent子组件 - 子组件内部不应该修改
@Prop属性(虽然技术上可以修改,但修改不会同步到父组件,且不符合单向数据流原则)
在当前应用中,三个 CategoryContent 实例在 VideoApp 的 build() 中通过条件渲染创建,每个实例的 category 参数是固定的字符串字面量(‘电影’、‘电视剧’、‘综艺’),不会变化。因此 @Prop 的单向特性完全满足需求。
如果需要父组件的分类参数动态变化并同步到子组件,可以使用 @Link 替代 @Prop,实现双向绑定。但在分类页场景中,分类通常是固定的,不需要动态变化,因此 @Prop 是更合适的选择。
10.5 组件复用的边界
虽然组件复用有诸多优势,但也需要注意复用的边界。不是所有相似的页面都适合复用同一组件,以下情况需要考虑拆分:
- 交互逻辑差异大:如果两个页面的布局相似但交互逻辑差异很大(如一个需要搜索功能,另一个不需要),强行复用会导致组件内部充满条件判断,降低代码可读性
- 数据结构差异大:如果两个页面的数据结构不同(如一个展示视频,另一个展示直播),复用组件需要处理多种数据类型,增加复杂度
- 视觉设计差异大:如果两个页面的视觉设计有明显差异(如一个是双列宫格,另一个是单列列表),复用组件需要大量的条件渲染,不如拆分为两个组件
在当前应用中,三个分类页的布局、交互、数据结构完全一致,只是内容数据不同,因此复用 CategoryContent 组件是最佳选择。这是组件复用的理想场景。
第十一章 我的页面与用户系统
11.1 我的页面布局
我的页面是视频 App 用户体系的入口,虽然当前仅展示未登录状态,但页面结构已经为完整的用户系统预留了框架。页面包含三个区域:
用户信息卡:位于页面顶部,背景色 SP.card,圆角 12px,内边距 20px,margin top 16px。包含三个元素:
- 圆形头像:emoji 👤,fontSize 48px,背景色
SP.cardLight,圆角 50px(圆形),宽度 80px,高度 80px,textAlign Center - "未登录"文字:fontSize 16px,fontWeight Bold,fontColor
SP.text,margin top 8px - "点击登录"提示:fontSize 12px,fontColor
SP.primary(橙色),margin top 4px
用户信息卡是我的页面最核心的区域,展示用户的基本信息。未登录状态下,头像使用默认 emoji,用户名显示"未登录",橙色"点击登录"提示引导用户登录。登录后,这个区域会替换为用户的真实头像、昵称、等级、VIP 状态等信息。
菜单列表:位于用户信息卡下方,背景色 SP.card,圆角 12px,左右内边距 12px,margin top 16px。包含 4 个菜单项,每个菜单项通过 @Builder menuItem(icon, label) 生成:
- 📺 我的收藏
- ⏳ 观看历史
- 💬 我的评论
- ⚙️ 设置
每个菜单项的结构为:Row 水平布局,左侧图标(fontSize 18px,margin right 12px)+ 标签(fontSize 14px,fontColor SP.text),中间 Column().layoutWeight(1) 撑开空间,右侧箭头 “>”(fontColor SP.textSub)。菜单项高度通过上下 padding 12px 控制,底部有 0.5px 的 SP.border 分割线。
底部标语:“— 影音有你,精彩无限 —”,fontSize 10px,fontColor SP.textSub,margin top 20px bottom 16px。作为页面的装饰性结尾,传递品牌理念。
11.2 @Builder menuItem 自定义构建函数
菜单项通过 @Builder menuItem(icon: string, label: string) 自定义构建函数生成。@Builder 是 ArkUI 提供的自定义构建函数装饰器,用于封装可复用的 UI 片段。
@Builder
menuItem(icon: string, label: string) {
Row() {
Text(icon)
.fontSize(18)
.margin({ right: 12 });
Text(label)
.fontSize(14)
.fontColor(SP.text);
Column().layoutWeight(1);
Text('>')
.fontColor(SP.textSub);
}
.width('100%')
.padding({ top: 12, bottom: 12 })
.border({ width: { bottom: 0.5 }, color: SP.border });
}
@Builder 函数的特点:
- 可复用:定义一次,可以在
build()中多次调用,每次调用传入不同的参数生成不同的 UI - 参数化:支持传入参数(如 icon、label),根据参数动态生成 UI
- 类型安全:参数有明确的类型标注(
icon: string、label: string),调用时传入错误类型会在编译时报错 - 简洁调用:在
build()中通过this.menuItem('📺', '我的收藏')调用,语法简洁
使用 @Builder 封装菜单项,避免了在 build() 中重复编写 4 个几乎相同的 Row 布局,提高了代码的可读性和可维护性。如果需要修改菜单项的样式(如调整图标大小、修改分割线颜色),只需要修改 @Builder menuItem 一处,4 个菜单项会同时更新。
11.3 未登录状态设计
我的页面当前展示未登录状态,这是视频 App 用户系统的常见设计。未登录状态的设计要点:
- 引导登录:橙色"点击登录"文字是页面中最醒目的交互元素,引导用户点击登录。橙色在深色背景上具有极高的视觉穿透力,用户很容易注意到
- 降低门槛:未登录状态下,页面依然展示了菜单列表(收藏、历史、评论、设置),用户可以浏览菜单结构,了解登录后可以使用的功能。这种"先展示后登录"的设计降低了用户的心理门槛,让用户在登录前就知道应用能提供什么
- 状态清晰:"未登录"文字明确告知用户当前状态,避免用户困惑。头像使用默认 emoji,与登录后的用户头像形成区分
- 登录入口:整个用户信息卡可以设置点击事件,点击后跳转到登录页面。当前应用未实现点击事件,但预留了交互入口
在真实视频 App 中,未登录状态通常还会展示一些不需要登录就能使用的功能(如浏览、搜索、观看免费内容),而需要登录的功能(如收藏、历史记录同步、评论、个性化推荐)在用户点击时引导登录。这种"部分功能免登录"的设计平衡了用户体验和用户转化。
11.4 用户系统的扩展方向
当前我的页面仅展示未登录状态和静态菜单,完整的视频 App 用户系统还需要以下功能:
- 用户登录/注册:支持手机号、微信、QQ、微博等多种登录方式,新用户注册流程
- 个人资料:头像、昵称、性别、生日、个性签名等个人信息的查看和编辑
- 会员系统:VIP 会员等级、会员权益、会员到期时间、续费入口等
- 我的收藏:用户收藏的影片列表,支持分类管理、取消收藏、批量操作
- 观看历史:用户观看过的影片列表,按时间排序,支持删除、清空、继续观看
- 我的评论:用户发表过的评论列表,支持查看、删除、回复
- 下载管理:用户下载的离线视频列表,支持下载、暂停、删除、播放
- 消息通知:系统通知、影片更新提醒、评论回复通知等
- 设置:播放设置、画质设置、缓存清理、账号安全、关于等
- 退出登录:退出当前账号,清除本地用户数据
这些功能构成了完整的视频 App 用户系统。当前应用的我的页面为这些功能的扩展提供了基础框架,菜单项已经预留了收藏、历史、评论、设置四个核心入口,后续可以逐步实现。
11.5 用户系统的设计价值
用户系统是视频 App 的核心基础设施,其设计价值体现在:
- 个性化体验:基于用户的观看历史、收藏、评分等数据,提供个性化推荐,提升用户发现内容的效率和满意度
- 跨设备同步:用户的收藏、历史记录、播放进度等数据在云端同步,用户在手机、平板、电视等不同设备上可以无缝切换
- 用户留存:会员系统、观看历史、收藏等功能增加了用户的迁移成本,用户在一个平台上积累的数据越多,越不容易流失到其他平台
- 商业化基础:用户系统是会员订阅、广告精准投放、内容付费等商业化模式的基础,没有用户系统就无法实现精准的商业变现
- 社区互动:评论、评分、分享等社区功能需要用户系统支撑,用户通过社区互动增强对平台的归属感和粘性
虽然当前概念应用未实现完整的用户系统,但我的页面的设计已经体现了对用户系统的思考和规划。未登录状态的引导、菜单列表的功能预留,都为后续用户系统的扩展打下了基础。
第十二章 流媒体文化与视频平台趋势
12.1 流媒体的崛起与变革
过去十年,流媒体彻底改变了人们观看视频内容的方式。从 Netflix 在 2013 年推出《纸牌屋》开启原创内容时代,到 Disney+、Apple TV+、HBO Max 等新玩家的入场,全球流媒体市场已经形成了群雄逐鹿的格局。在中国市场,爱奇艺、腾讯视频、优酷构成了长视频的"爱腾优"三巨头,芒果 TV 凭借综艺内容差异化突围,Bilibili 从二次元社区成长为综合视频平台,抖音、快手等短视频平台也在向长视频领域渗透。
流媒体的崛起带来了几个根本性的变革:
- 观看方式的变革:从"电视台排什么看什么"的线性播放,到"想看什么看什么、想什么时候看就什么时候看"的按需播放,用户获得了完全的观看自主权
- 内容生产的变革:流媒体平台投入巨资制作原创内容,《纸牌屋》《怪奇物语》《权力的游戏》《狂飙》《三体》等原创剧集的质量已经超越传统电视台,"电影级制作"成为剧集的新标准
- 商业模式的变革:从广告支持的免费模式,到会员订阅模式,再到会员+广告的混合模式,流媒体平台不断探索可持续的商业模式
- 观看设备的变革:从电视到电脑,从手机到平板,从智能电视到投影,用户可以在各种设备上观看流媒体内容,跨设备同步成为标配
- 推荐算法的变革:基于用户行为数据的个性化推荐算法成为流媒体平台的核心竞争力,"猜你喜欢"的推荐准确率直接影响用户的观看时长和平台留存
12.2 爱奇艺的设计语言
爱奇艺作为中国领先的流媒体平台,其设计语言具有鲜明的特点:
- 橙色品牌色:爱奇艺的品牌色是橙色(#FF6A00 或类似色值),橙色代表活力、热情、娱乐,与视频平台的娱乐属性高度契合。橙色在深色背景上具有极高的视觉穿透力,是爱奇艺最核心的视觉识别元素
- 深色模式优先:爱奇艺 App 采用深色模式作为默认主题,深色背景降低了夜间观影的视觉刺激,同时让视频内容(通常是彩色的)在深色背景上更加突出。深色模式已经成为流媒体 App 的行业标准
- 首页信息流:爱奇艺首页采用多模块信息流布局,顶部是搜索框和频道导航,下方依次是横向滚动的 Banner、热门推荐、频道分类、精选内容等模块,用户可以纵向滚动浏览大量内容
- 视频卡片化:内容以卡片形式展示,每个卡片包含封面图、标题、评分、标签等信息。卡片化布局提高了内容的浏览效率,用户可以快速扫描大量卡片找到感兴趣的内容
- 播放页沉浸设计:视频播放页采用全屏沉浸设计,隐藏系统状态栏和导航栏,让用户专注于视频内容。播放控制栏半透明悬浮,不遮挡视频画面
- 会员体系视觉:VIP 会员在界面中有专属的金色标识和视觉元素,会员内容有专属角标,强化会员的尊贵感和权益感知
这款概念应用在设计上参考了爱奇艺的设计语言,特别是橙色品牌色、深色模式、视频卡片化、底部 Tab 导航等核心元素,还原了爱奇艺风格的视觉体验。
12.3 视频平台的内容竞争
内容是视频平台的核心竞争力。各大平台在内容上的竞争主要体现在以下几个方面:
- 独家原创内容:原创内容是平台差异化的核心武器。爱奇艺的"迷雾剧场"(《隐秘的角落》《沉默的真相》)、腾讯视频的"鬼吹灯"系列、优酷的"白夜追凶"、芒果 TV 的"乘风破浪的姐姐"等,都是平台的独家内容名片
- 版权采购:除了原创内容,平台还需要采购电影、电视剧、综艺、动漫、纪录片等各类内容的版权。热门内容的独家版权往往成为平台竞争的焦点
- 内容矩阵:成熟的视频平台需要构建覆盖电影、电视剧、综艺、动漫、纪录片、少儿、体育等多个品类的内容矩阵,满足不同用户群体的内容需求
- 内容质量:在内容数量趋于饱和的背景下,内容质量成为竞争的关键。高口碑、高热度的"爆款"内容能够带来大量的新用户和会员转化
- 内容时效性:新片、新剧的上线速度是用户选择平台的重要因素。“全网首播”、"独家首播"等时效性标签对用户有很强的吸引力
这款概念应用的 12 部影片涵盖了电影、电视剧、综艺三个品类,每部都有较高的评分和口碑,体现了对内容质量的重视。在真实应用中,内容库的规模和质量是平台竞争力的直接体现。
12.4 短视频的冲击与长视频的应对
近年来,抖音、快手等短视频平台的崛起对长视频平台造成了巨大的冲击。短视频以其碎片化、高沉浸、算法推荐精准等特点,占据了用户大量的娱乐时间,挤压了长视频的观看时长。
面对短视频的冲击,长视频平台采取了以下应对策略:
- 布局短视频:爱奇艺推出"随刻"、腾讯视频推出"短视频"频道、优酷推出"短视频"模块,长视频平台纷纷在自己的 App 中嵌入短视频内容,试图在内部消化短视频的冲击
- 强化长视频优势:长视频平台强调自己在精品内容、沉浸式观看、完整叙事等方面的优势,与短视频的碎片化内容形成差异化。剧集、电影等长内容的叙事深度和情感沉浸是短视频无法替代的
- 互动视频创新:尝试互动剧、互动电影等新形式,让用户参与到剧情发展中,提升观看的参与感和趣味性
- 社区化运营:增强评论、弹幕、评分、分享等社区功能,让用户在观看内容的同时进行社交互动,提升用户粘性
- 会员价值深化:通过会员专属内容、会员抢先看、会员画质提升、会员免广告等权益,深化会员价值,提高会员续费率
这款概念应用作为长视频平台的设计参考,聚焦于长视频的核心体验:内容浏览、分类导航、个性化推荐。这些是长视频平台相对于短视频的核心优势,也是长视频平台需要持续强化的方向。
12.5 流媒体的未来趋势
展望未来,流媒体行业的发展趋势包括:
- AI 赋能:人工智能将在内容推荐、内容制作、画质增强、字幕生成等多个环节发挥重要作用。AI 推荐算法将更加精准,AI 生成内容(AIGC)将降低内容制作成本
- 沉浸式体验:VR/AR 技术的成熟将带来沉浸式的观影体验,虚拟影院、VR 直播等新形式将涌现
- 多屏协同:手机、平板、电视、投影、车载屏幕等多设备的协同将更加紧密,用户可以在不同设备间无缝切换观看
- 社交化观看:一起看、观影派对、实时评论等社交化观看功能将更加普及,让独自观看变成社交体验
- 精细化运营:基于用户数据的精细化运营将成为常态,不同用户群体看到不同的首页布局、内容推荐、会员套餐,实现"千人千面"
- 内容全球化:流媒体平台的内容将更加全球化,韩剧、美剧、日剧、泰剧等海外内容在中国市场的影响力将持续增强,中国内容也将加速出海
这款概念应用虽然是一个简化的 UI 原型,但它所体现的深色观影体验、橙色品牌识别、卡片化内容布局、Tab 导航结构等设计元素,正是当前流媒体行业的主流设计范式。理解这些设计元素背后的文化和趋势,有助于我们更好地设计和开发面向未来的视频应用。
第十三章 深色模式与观影体验
13.1 深色模式的兴起
深色模式(Dark Mode)是近年来 UI 设计领域最重要的趋势之一。从 macOS Mojave 在 2018 年引入系统级深色模式,到 iOS 13、Android 10 相继跟进,深色模式已经从"小众偏好"变成了"系统标配"。如今,几乎所有主流 App 都提供了深色模式选项,很多 App 甚至将深色模式作为默认主题。
深色模式的兴起有以下几个原因:
- OLED 屏幕的普及:OLED 屏幕的每个像素可以独立发光,显示黑色时像素完全关闭,不消耗电量。深色模式在 OLED 屏幕上可以显著降低功耗,延长续航时间。随着 OLED 屏幕在中高端手机上的普及,深色模式的省电优势越来越明显
- 视觉健康关注:长时间盯着明亮的白色屏幕会导致眼睛疲劳、干涩、视力下降。深色模式降低了屏幕的整体亮度,减少了眼睛的调节负担,特别适合在夜间或低光环境下使用
- 沉浸感需求:深色背景让内容(图片、视频、文字)更加突出,减少了界面元素对内容的干扰,创造了更强的沉浸感。对于视频、图片、阅读等内容消费类应用,深色模式的沉浸感优势尤为明显
- 设计美学驱动:深色模式具有独特的视觉美感,高对比度的色彩在深色背景上更加鲜艳和醒目,为设计师提供了新的创作空间。很多 App 在深色模式下的视觉效果比浅色模式更加惊艳
- 系统级支持:iOS、Android、macOS、Windows 等主流操作系统都提供了系统级的深色模式开关和开发者 API,App 可以轻松适配系统深色模式,降低了开发成本
13.2 视频 App 与深色模式的天然契合
视频 App 是深色模式的最佳应用场景之一,二者具有天然的契合性:
- 观影场景的低光环境:用户观看视频的场景通常是在室内、夜间等低光环境下。在低光环境中,明亮的白色界面会与视频画面形成强烈的明暗对比,导致眼睛频繁调节,容易疲劳。深色模式的界面与低光环境更加协调,减少了视觉刺激
- 视频内容的突出展示:视频内容本身是彩色的、动态的,在深色背景上更加突出。如果界面是白色的,白色背景会分散用户对视频内容的注意力。深色背景让用户的视觉焦点自然集中在视频内容上
- 全屏观影的沉浸感:视频播放时通常会进入全屏模式,隐藏系统状态栏和导航栏。深色模式的界面元素(如播放控制栏、进度条)在全屏模式下与黑色的视频留白区域融为一体,不会破坏沉浸感
- 续航考虑:视频播放是手机的高功耗场景,屏幕常亮、解码器运行、网络传输等都在消耗电量。深色模式在 OLED 屏幕上可以降低屏幕功耗,延长观影时间,这对用户来说是实实在在的体验提升
- 行业标准的形成:Netflix、YouTube、爱奇艺、腾讯视频等主流视频平台都采用了深色模式作为默认主题,深色模式已经成为视频 App 的行业标准。用户在使用视频 App 时已经形成了"视频 App 就是深色的"的认知预期
这款概念应用完全采用深色模式设计,背景色 #121212 接近纯黑,卡片色 #1E1E1E 和浅卡片色 #2A2A2A 构建了清晰的视觉层级,橙色主色 #FF6A00 在深色背景上具有极强的视觉穿透力。整套色彩系统是视频 App 深色模式设计的典范。
13.3 深色模式的色彩设计原则
深色模式不是简单地"把白色背景换成黑色、把黑色文字换成白色",而是需要一套完整的色彩设计原则。这款应用的深色模式色彩设计体现了以下原则:
- 避免纯黑背景:纯黑(#000000)在 OLED 屏幕上虽然最省电,但与纯白文字的对比度过高,容易产生眩光感,同时纯黑背景上的阴影和层级难以表现。这款应用使用 #121212 作为背景色,接近纯黑但保留了极微弱的灰度,既降低了视觉刺激,又为视觉层级留出了空间
- 灰度构建层级:深色模式下,不同区域的区分不能依赖边框(边框在深色背景上不明显),而需要依赖灰度层级。这款应用使用三级灰度:背景 #121212、卡片 #1E1E1E、浅卡片 #2A2A2A,通过微妙的亮度差异区分不同的视觉区域
- 降低文字亮度:纯白(#FFFFFF)文字在深色背景上对比度极高,长时间阅读容易疲劳。这款应用使用纯白 #FFFFFF 作为主文字色(用于标题等短文本),使用中灰 #AAAAAA 作为次级文字色(用于描述等长文本)。次级文字降低了亮度,减少了长时间阅读的视觉疲劳
- 饱和色的克制使用:高饱和色在深色背景上非常醒目,但过度使用会造成视觉混乱。这款应用的橙色主色 #FF6A00 仅用于关键交互元素(选中态、评分、热门标签),不滥用,确保橙色的视觉权重
- 对比度达标:深色模式下的文字与背景对比度需要满足 WCAG AA 级标准(4.5:1 以上)。这款应用的纯白文字 #FFFFFF 与背景 #121212 的对比度约为 18:1,中灰文字 #AAAAAA 与背景的对比度约为 5.5:1,均满足 AA 级标准
- 半透明叠加:深色模式下常使用半透明叠加层(如
SP.primary + '33'表示 20% 透明度的橙色)来创建选中态、标签背景等。半透明叠加在深色背景上效果自然,不会产生生硬的色块
13.4 观影体验的设计要素
除了深色模式,视频 App 的观影体验还涉及以下设计要素:
- 播放页沉浸设计:视频播放页是观影体验的核心。优秀的播放页应该在播放时隐藏所有非必要的 UI 元素(状态栏、导航栏、标题栏),让视频内容占据整个屏幕。播放控制栏(播放/暂停、进度条、倍速、画质、全屏等)应该半透明悬浮在视频画面上,用户不操作时自动隐藏,点击屏幕时呼出
- 手势操作:视频播放页应该支持丰富的手势操作,如单击呼出/隐藏控制栏、双击播放/暂停、左侧上下滑动调节亮度、右侧上下滑动调节音量、横向滑动快进/快退等。手势操作减少了对按钮的依赖,提升了操作效率和沉浸感
- 画质选择:提供多种画质选项(流畅 360P、标清 480P、高清 720P、超清 1080P、蓝光 4K 等),用户可以根据网络状况和设备性能选择合适的画质。WiFi 环境下默认高清,移动网络下提示流量消耗
- 倍速播放:提供 0.5x、0.75x、1.0x、1.25x、1.5x、2.0x 等多种倍速选项,满足不同用户的观看需求。倍速播放是年轻用户的常用功能,可以快速浏览内容或仔细观看细节
- 字幕与音轨:对于多语言内容,提供字幕语言选择和音轨选择。字幕的字体大小、颜色、背景透明度等也可以自定义,满足不同用户的阅读需求
- 投屏与跨设备:支持投屏到电视、投影等大屏设备,支持手机与平板、电视等设备的跨设备续播,用户可以在不同设备间无缝切换
- 离线下载:支持视频离线下载,用户可以在 WiFi 环境下下载视频,在没有网络的环境下观看。下载管理支持画质选择、下载任务管理、下载完成通知等
这款概念应用未实现视频播放功能,仅实现了内容浏览的 UI。但作为视频 App 的设计参考,理解上述观影体验的设计要素,有助于在后续开发中构建完整的视频播放体验。
13.5 深色模式的适配策略
在真实的视频 App 开发中,深色模式的适配通常有以下几种策略:
- 固定深色模式:App 始终使用深色模式,不提供浅色模式选项。这种策略适用于视频、图片、阅读等内容消费类 App,深色模式是这些 App 的最佳体验。Netflix、YouTube(移动端)等 App 采用这种策略
- 跟随系统:App 跟随系统的深色模式设置,系统开启深色模式时 App 自动切换到深色模式,系统关闭时切换到浅色模式。这种策略是目前的主流做法,用户不需要在 App 内单独设置
- App 内开关:App 内提供深色模式开关,用户可以手动选择浅色、深色或跟随系统。这种策略给用户最大的自由度,但增加了开发和维护成本
- 定时切换:App 根据时间自动切换深色模式,如晚上 8 点到早上 6 点自动使用深色模式,其他时间使用浅色模式。这种策略适用于阅读类 App,符合人体的生物钟规律
这款概念应用采用固定深色模式策略,整个 App 只有深色主题,不提供浅色模式选项。这是视频 App 的常见做法,因为深色模式与视频观影场景天然契合,浅色模式在视频 App 中反而会降低观影体验。
13.6 深色模式与 accessibility
深色模式的设计还需要考虑无障碍(accessibility)需求:
- 对比度:确保文字与背景的对比度满足 WCAG AA 级标准(4.5:1),对于大字体(18pt 以上或 14pt 加粗)可以降低到 3:1。高对比度确保视力障碍用户也能清晰阅读
- 不要仅依赖颜色传达信息:不要仅通过颜色区分不同状态(如仅用红色表示错误、绿色表示成功),还需要配合图标、文字等其他视觉线索。例如,热门标签不仅用橙色背景,还有"🔥 热门"文字和图标
- 支持系统辅助功能:支持系统的"增强对比度"、“降低透明度”、"智能反转"等辅助功能设置。当用户开启这些功能时,App 应该相应调整界面,确保可用性
- 字体大小适配:支持系统的字体大小设置,当用户调大系统字体时,App 的文字也应该相应放大,同时布局不能错乱。使用相对单位(如 sp、%)而非固定像素值,有助于字体大小的适配
这款概念应用在设计上考虑了对比度和信息传达的多样性,但作为概念原型,未实现系统辅助功能的完整适配。在真实应用开发中,无障碍适配是不可忽视的重要环节。
深色模式不仅仅是一种视觉风格,更是一种以用户体验为中心的设计哲学。它关注用户在不同环境下的视觉舒适度,关注内容与界面的视觉层级,关注续航和性能,关注无障碍和包容性。这款爱奇艺风格视频 App 的深色模式设计,正是这种设计哲学的体现——通过精心调配的色彩系统,为用户创造一个沉浸式、舒适、高效的观影体验。
第十四章 开发环境搭建与运行指南
本章为安装软件步骤,按要求放在最后一章。
14.1 开发环境概述
爱奇艺风格视频 App 基于 HarmonyOS ArkTS 声明式 UI(ArkUI)开发,API 9+,Stage 模型,单文件 .ets,严格遵循 ArkTS 严格模式规范(无 Object.assign,无展开运算符,显式类型标注)。开发环境需要:DevEco Studio(HarmonyOS 官方 IDE)、HarmonyOS SDK(API 9+)、Node.js(构建工具依赖)、HarmonyOS 设备或模拟器(运行调试)。
14.2 安装 DevEco Studio
DevEco Studio 是华为官方推出的 HarmonyOS 应用开发 IDE,基于 IntelliJ IDEA 社区版构建,提供 ArkTS/ArkUI 代码编辑、预览、调试、打包等完整开发能力。
安装步骤:
- 访问华为开发者联盟官网(developer.huawei.com),进入"开发"→"DevEco Studio"下载页面
- 选择对应操作系统版本(Windows/macOS)下载安装包
- Windows 版运行 .exe 安装程序,按向导完成安装(建议安装路径不含中文和空格)
- macOS 版打开 .dmg 镜像,将 DevEco Studio 拖入 Applications 文件夹
- 首次启动 DevEco Studio,选择不导入设置,进入欢迎界面
- 配置 Node.js 路径(DevEco Studio 内置 Node.js,也可使用本地安装的 Node.js 16+)
- 配置 npm 镜像(建议使用华为云 npm 镜像或淘宝镜像,加速依赖下载)

14.3 配置 HarmonyOS SDK
DevEco Studio 安装完成后,需要配置 HarmonyOS SDK(API 9+)。SDK 包含 ArkTS 编译器、ArkUI 组件库、系统 API、模拟器镜像等。
配置步骤:
- 打开 DevEco Studio,进入欢迎界面
- 点击"Configure"→"Settings"(macOS 为"DevEco Studio"→"Settings")
- 左侧导航选择"Appearance & Behavior"→"System Settings"→"HarmonyOS SDK"
- 在 SDK Platforms 标签页,勾选 API 9 或更高版本(建议 API 9,兼容性最好)
- 在 SDK Tools 标签页,勾选"ArkTS"、“ArkUI”、"Previewer"等工具
- 点击"Apply"开始下载安装 SDK(下载大小约 2-4GB,需耐心等待)
- 安装完成后,SDK 路径会自动配置,可在 Settings 中查看和修改
SDK 版本说明:
- API 9:首个支持 Stage 模型和 ArkTS 声明式 UI 的稳定版本,兼容性最好
- API 10+:新增更多 ArkUI 组件和 API,功能更丰富,但兼容性需考虑
- 建议使用 API 9 开发,确保在更多设备上运行

14.4 创建 HarmonyOS 项目
SDK 配置完成后,创建一个新的 HarmonyOS 项目。
创建步骤:
- 打开 DevEco Studio,进入欢迎界面
- 点击"Create Project"创建新项目
- 选择项目模板:选择"Empty Ability"(空能力模板),这是最基础的 ArkTS 项目模板
- 配置项目信息:
- Project name:项目名称(如 VideoApp)
- Bundle name:应用包名(如 com.example.videoapp,需唯一)
- Save location:项目保存路径(建议不含中文和空格)
- Compile SDK:选择 API 9 或更高版本
- Model:选择 Stage 模型(ArkTS 声明式 UI 推荐使用 Stage 模型)
- Language:选择 ArkTS
- 点击"Finish"创建项目,DevEco Studio 会自动生成项目结构和基础代码
- 等待项目同步完成(Gradle 同步,首次同步可能需要几分钟下载依赖)
项目结构说明:
entry/src/main/ets/:ArkTS 源代码目录entry/src/main/ets/entryability/:Ability 入口文件entry/src/main/ets/pages/:页面文件目录entry/src/main/resources/:资源文件目录(图片、字符串、主题等)entry/src/main/module.json5:模块配置文件build-profile.json5:构建配置文件
创建项目后,将视频 App 的完整代码(VideoApp.ets)复制到 entry/src/main/ets/pages/ 目录下,或替换默认的 Index.ets 文件。注意修改 @Entry 装饰器的组件名和 module.json5 中的入口页面配置。

14.5 运行应用
项目创建和代码导入完成后,可以在真机或模拟器上运行应用。
运行步骤:
- 连接 HarmonyOS 设备(通过 USB 数据线),或启动模拟器
- 真机运行需开启开发者模式和 USB 调试:设置→关于手机→连续点击版本号开启开发者模式→设置→系统和更新→开发人员选项→开启 USB 调试
- 模拟器运行:点击 DevEco Studio 顶部工具栏的设备选择器,选择"Device Manager"→创建模拟器→选择设备型号和 API 版本→启动模拟器
- 在 DevEco Studio 顶部工具栏,确认运行配置为"entry",设备选择为已连接的真机或已启动的模拟器
- 点击运行按钮(绿色三角形 ▶)或按快捷键 Shift+F10(macOS 为 Control+R)
- DevEco Studio 会自动编译、打包、安装应用到设备,并启动应用
- 首次运行可能需要 1-2 分钟编译时间,后续运行会更快(增量编译)
运行注意事项:
- 确保设备已开启 USB 调试,且已授权电脑调试
- 确保设备系统版本 >= API 9(HarmonyOS 3.0+)
- 模拟器运行性能可能不如真机,建议最终在真机上测试
- 如果运行失败,查看 Build 窗口和 Log 窗口的错误信息
视频 App 的 Tab 图标缩放交互动画建议在真机上测试,确保动画流畅度和交互反馈符合预期。预览器可以查看静态布局,但动画效果需要在真机或模拟器上运行才能完整体验。

14.6 预览与调试
DevEco Studio 提供 ArkUI 预览器和调试工具,方便开发过程中实时查看界面和调试代码。
预览器使用:
- 打开 .ets 页面文件,右侧会自动显示 Previewer 预览面板
- 预览器实时渲染当前页面的 ArkUI 界面,代码修改后自动刷新
- 预览器支持多种设备尺寸和方向切换,测试不同屏幕适配
- 预览器支持深色/浅色模式切换(视频 App 为深色主题,建议在深色模式下预览)
- 如果预览器未显示,点击右侧边栏的"Previewer"图标开启
调试工具使用:
- 在代码行号左侧点击设置断点(红色圆点)
- 点击调试按钮(绿色虫子 🐛)或按快捷键 Shift+F9(macOS 为 Control+D)
- 应用运行到断点处会暂停,可查看变量值、调用栈、表达式等
- 使用调试工具栏的单步跳过(F8)、单步进入(F7)、单步跳出(Shift+F8)等操作逐步执行代码
- 在 Variables 面板查看和修改变量值,在 Watches 面板添加监视表达式
- 在 Log 面板查看应用日志(console.log 输出和系统日志)
常见问题排查:
- 预览器不显示:检查代码是否有语法错误,查看 Previewer 面板的错误提示
- 运行失败:检查 SDK 版本是否匹配,查看 Build 窗口的编译错误
- 界面显示异常:检查 ArkUI 组件属性是否正确,使用预览器实时调试
- 动画不流畅:检查是否有过多同时运行的动画,优化动画时长和曲线
- ArkTS 严格模式报错:检查是否使用了 Object.assign、是否使用了展开运算符(…)、是否有隐式类型、是否缺少分号
视频 App 严格遵循 ArkTS 严格模式规范(无 Object.assign,无展开运算符,显式类型标注,分号结尾),在严格模式下可正常编译运行。开发过程中如遇到严格模式报错,可对照规范检查代码。

总结与展望
核心回顾
爱奇艺风格视频 App 是一款基于 HarmonyOS ArkTS 声明式 UI(ArkUI)开发的流媒体概念应用,API 9+,Stage 模型,单文件 .ets,约 500 行代码,严格遵循 ArkTS 严格模式规范。应用以深黑 #121212 为背景,以爱奇艺标志性的橙色 #FF6A00 为主强调色,构建了一个简洁而高效的移动端视频浏览体验。
5 个 Tab 覆盖主流视频平台的核心内容分区:首页(横向滚动推荐+分类筛选chips+双列宫格)、电影(分类内容页,4部影片)、电视剧(分类内容页,4部剧集)、综艺(分类内容页,4部综艺)、我的(未登录状态+菜单列表)。12 部影片涵盖电影、电视剧、综艺三个品类,每部都有评分、年份、简介等完整信息。3 部热门影片(流浪地球2、狂飙、三体)通过 FEATURED 派生用于首页横向推荐。
应用的技术亮点包括:组件化架构(VideoHeader/HomeContent/CategoryContent/MineContent 四大组件)、@Link 双向绑定与可选回调(搜索文本父子组件通信)、@Prop 分类参数传递(CategoryContent 三分类复用)、横向+纵向双滚动(首页推荐横向滚动+内容纵向滚动)、Flex 双列宫格布局、Tab 图标缩放动画(scale 1.2,200ms EaseOut)、2 个纯函数工具(genId/formatScore)、Record<string,string> 类型安全映射、ArkTS 严格模式合规(无 Object.assign/无展开运算符/显式类型/分号结尾)。
设计亮点
- 爱奇艺橙色品牌色:#FF6A00 精确还原爱奇艺品牌橙色,在深色背景上具有极强的视觉识别度,传递活力与娱乐感
- 深色观影沉浸体验:深黑 #121212 背景+三级灰度层级(背景/卡片/浅卡片),营造沉浸式观影氛围,降低夜间视觉疲劳
- @Link + 可选回调双重通信:搜索文本通过 @Link 双向绑定 + onSearchChange 可选回调,既保证状态同步又提供事件通知,是 ArkUI 父子通信的经典模式
- CategoryContent 三分类复用:通过 @Prop category 参数,电影/电视剧/综艺三个 Tab 共用同一组件,代码复用率高,扩展性强
- 首页三层漏斗推荐:横向推荐(强推)→分类筛选(导航)→双列宫格(穷尽),覆盖用户从被动接受到主动探索的完整行为路径
- 双列宫格响应式布局:Flex wrap + SpaceBetween + 47% 宽度,在不同屏幕宽度下保持双列布局,卡片宽度自适应
- ArkTS 严格模式深度合规:无 Object.assign,无展开运算符(…),所有变量和函数参数显式类型标注,Record 类型安全映射,类型安全无运行时错误
- 单文件零依赖:约 500 行代码在一个 .ets 文件中,复制即运行,无第三方依赖,适合学习和快速原型验证
技术价值
爱奇艺风格视频 App 展示了 ArkTS 声明式 UI 在流媒体应用开发中的完整能力,特别是组件化架构和状态管理的最佳实践:@Component/@Entry 组件化、@State 组件内状态、@Link 父子双向绑定、@Prop 父子单向传参、可选回调函数事件通知、@Builder 自定义构建函数封装可复用 UI、ForEach 列表渲染、Scroll 横向/纵向滚动、Flex 响应式布局、TextInput 搜索框、属性动画(.animation())、纯函数工具、Record 类型安全映射。
特别是 @Link + 可选回调 的双重通信模式和 CategoryContent 的 @Prop 组件复用设计,是 ArkUI 组件化开发的高级技巧,具有很高的参考价值。这些模式可以应用到任何需要父子组件通信和多页面复用的 ArkUI 项目中。
未来展望
爱奇艺风格视频 App 作为概念应用,未来可扩展方向:
- 真实视频播放:接入视频播放器组件,实现视频播放、暂停、进度条、倍速、画质选择、全屏等完整播放功能
- 真实图片替换 emoji:将封面 emoji 替换为真实的电影海报和剧集封面,提升视觉真实感和专业度
- 搜索功能完善:实现搜索文本对视频列表的实时过滤,支持标题搜索、分类+搜索组合过滤、搜索历史记录、热门搜索推荐
- 视频详情页:点击视频卡片跳转到详情页,展示详细信息(导演、演员、简介、分集列表、相关推荐)和播放按钮
- 用户登录系统:实现手机号/微信/QQ 登录,用户个人资料、头像、昵称等
- 收藏与历史记录:实现视频收藏、观看历史记录、继续观看等功能,数据本地存储或云端同步
- 评论与弹幕:实现视频评论、弹幕、评分等社区互动功能
- 会员系统:实现 VIP 会员体系,会员专属内容、会员免广告、会员画质提升等
- 个性化推荐:基于用户观看历史和收藏数据,实现个性化推荐算法,提升内容发现效率
- 多端适配:适配手机、平板、折叠屏、智能电视等多种设备,支持横屏和分屏模式
爱奇艺风格视频 App 以深色观影体验和橙色品牌活力为核心视觉语言,以"首页推荐+内容分类+搜索筛选+个人中心"四位一体功能,展示了流媒体 App 的经典设计范式。它不仅是一个概念应用,更是 ArkTS 严格模式开发的完整教学案例,涵盖组件化架构、状态管理、组件复用、响应式布局、交互动画等核心技术点。希望本文能为 HarmonyOS 开发者和视频 App 从业者提供有价值的参考。
影音有你,精彩无限。🎬

附录:完整 ArkTS 源码
// ============================================================
// VideoApp.ets —— 爱奇艺风格视频 App(修正版)
// 主题:深色底 #121212,主色 #FF6A00,5 Tab:首页/电影/电视剧/综艺/我的
// 适配 ArkTS 严格模式,无 Object.assign,无展开,显式类型
// ============================================================
// ---------- 调色板接口 ----------
interface VideoPalette {
bg: string;
primary: string;
primaryDark: string;
card: string;
cardLight: string;
text: string;
textSub: string;
chip: string;
border: string;
white: string;
}
const SP: VideoPalette = {
bg: '#121212',
primary: '#FF6A00',
primaryDark: '#CC5500',
card: '#1E1E1E',
cardLight: '#2A2A2A',
text: '#FFFFFF',
textSub: '#AAAAAA',
chip: '#333333',
border: '#444444',
white: '#FFFFFF'
};
// ---------- 接口定义 ----------
interface VideoItem {
id: number;
title: string;
cover: string; // emoji 占位
category: string; // '电影' | '电视剧' | '综艺'
score: number; // 评分 0-10
year: string;
desc: string;
hot: boolean;
}
interface CategoryTab {
name: string;
key: string;
}
// ---------- 静态数据 ----------
const CATEGORIES: CategoryTab[] = [
{ name: '全部', key: 'all' },
{ name: '电影', key: 'movie' },
{ name: '电视剧', key: 'tv' },
{ name: '综艺', key: 'variety' }
];
const VIDEOS: VideoItem[] = [
{ id: 1, title: '流浪地球2', cover: '🚀', category: '电影', score: 8.7, year: '2023', desc: '太阳即将毁灭,人类启动流浪地球计划。', hot: true },
{ id: 2, title: '狂飙', cover: '🚓', category: '电视剧', score: 9.2, year: '2023', desc: '正义与邪恶的较量,跨越二十年的追捕。', hot: true },
{ id: 3, title: '声生不息', cover: '🎤', category: '综艺', score: 8.5, year: '2022', desc: '港乐经典,群星演绎。', hot: false },
{ id: 4, title: '满江红', cover: '⚔️', category: '电影', score: 8.2, year: '2023', desc: '岳飞遗志,忠义传承。', hot: false },
{ id: 5, title: '三体', cover: '🌌', category: '电视剧', score: 8.9, year: '2023', desc: '科幻巨制,探索宇宙奥秘。', hot: true },
{ id: 6, title: '向往的生活', cover: '🌿', category: '综艺', score: 7.8, year: '2023', desc: '田园生活,慢综艺。', hot: false },
{ id: 7, title: '长津湖之水门桥', cover: '❄️', category: '电影', score: 8.4, year: '2022', desc: '抗美援朝,冰血长津。', hot: false },
{ id: 8, title: '梦华录', cover: '🌸', category: '电视剧', score: 8.6, year: '2022', desc: '宋代女性创业传奇。', hot: false },
{ id: 9, title: '披荆斩棘', cover: '🔥', category: '综艺', score: 8.1, year: '2023', desc: '哥哥们的舞台竞演。', hot: false },
{ id: 10, title: '独行月球', cover: '🌕', category: '电影', score: 7.9, year: '2022', desc: '月球上的孤独与欢乐。', hot: false },
{ id: 11, title: '警察荣誉', cover: '👮', category: '电视剧', score: 8.7, year: '2022', desc: '基层民警的日常。', hot: false },
{ id: 12, title: '乘风破浪', cover: '💃', category: '综艺', score: 8.3, year: '2022', desc: '姐姐们再度起航。', hot: false }
];
const FEATURED: VideoItem[] = VIDEOS.filter(v => v.hot).slice(0, 3);
// ---------- Tab 枚举 ----------
enum VideoTab {
HOME = 0,
MOVIE = 1,
TV = 2,
VARIETY = 3,
MINE = 4
}
// ---------- 辅助函数 ----------
function genId(): number {
return Math.floor(Math.random() * 1000000);
}
function formatScore(s: number): string {
return s.toFixed(1);
}
// ---------- 入口 ----------
@Entry @Component struct VideoApp {
@State activeTab: number = VideoTab.HOME;
@State searchText: string = '';
build() {
Column() {
VideoHeader({ searchText: $searchText, onSearchChange: (v: string) => { this.searchText = v; } });
Stack() {
if (this.activeTab === VideoTab.HOME) {
HomeContent();
} else if (this.activeTab === VideoTab.MOVIE) {
CategoryContent({ category: '电影' });
} else if (this.activeTab === VideoTab.TV) {
CategoryContent({ category: '电视剧' });
} else if (this.activeTab === VideoTab.VARIETY) {
CategoryContent({ category: '综艺' });
} else {
MineContent();
}
}
.layoutWeight(1)
.width('100%');
Row() {
this.tabItem('🏠', '首页', VideoTab.HOME);
this.tabItem('🎬', '电影', VideoTab.MOVIE);
this.tabItem('📺', '电视剧', VideoTab.TV);
this.tabItem('🎪', '综艺', VideoTab.VARIETY);
this.tabItem('👤', '我的', VideoTab.MINE);
}
.width('100%')
.height(56)
.backgroundColor(SP.card)
.padding({ left: 4, right: 4, top: 4 })
.border({ width: { top: 0.5 }, color: SP.border });
}
.width('100%')
.height('100%')
.backgroundColor(SP.bg);
}
@Builder
tabItem(icon: string, label: string, tab: number) {
Column() {
Text(icon)
.fontSize(22)
.scale({
x: this.activeTab === tab ? 1.2 : 1,
y: this.activeTab === tab ? 1.2 : 1
})
.animation({ duration: 200, curve: Curve.EaseOut });
Text(label)
.fontSize(10)
.fontColor(this.activeTab === tab ? SP.primary : SP.textSub)
.margin({ top: 2 });
}
.layoutWeight(1)
.onClick(() => {
this.activeTab = tab;
});
}
}
// ---------- 头部组件(修正 onSearchChange 为可选) ----------
@Component struct VideoHeader {
@Link searchText: string;
onSearchChange?: (v: string) => void; // 改为可选
build() {
Row() {
Text('🎬 影音')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.fontColor(SP.primary)
.margin({ left: 16 });
Column().layoutWeight(1);
Row() {
Text('🔍')
.fontSize(14)
.fontColor(SP.textSub)
.margin({ left: 8 });
TextInput({ placeholder: '搜索视频', text: this.searchText })
.fontSize(12)
.fontColor(SP.text)
.backgroundColor(Color.Transparent)
.borderRadius(0)
.height(30)
.layoutWeight(1)
.margin({ left: 4 })
.onChange((v: string) => {
this.searchText = v;
if (this.onSearchChange) {
this.onSearchChange(v);
}
});
}
.width(180)
.height(32)
.backgroundColor(SP.cardLight)
.borderRadius(16)
.margin({ right: 16 });
}
.width('100%')
.height(48)
.backgroundColor(SP.card)
.border({ width: { bottom: 0.5 }, color: SP.border });
}
}
// ---------- 首页内容 ----------
@Component struct HomeContent {
@State videos: VideoItem[] = VIDEOS;
@State selectedCategory: string = 'all';
build() {
Column() {
Scroll() {
Row() {
ForEach(FEATURED, (item: VideoItem) => {
Column() {
Text(item.cover)
.fontSize(60)
.textAlign(TextAlign.Center)
.width('100%')
.height(120)
.backgroundColor(SP.cardLight)
.borderRadius(8);
Text(item.title)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor(SP.text)
.margin({ top: 6 });
Text('🔥 热门')
.fontSize(10)
.fontColor(SP.primary)
.backgroundColor(SP.primary + '33')
.borderRadius(4)
.padding({ left: 6, right: 6, top: 2, bottom: 2 })
.margin({ top: 4 });
}
.width(140)
.backgroundColor(SP.card)
.borderRadius(12)
.padding(10)
.margin({ left: 10, right: 10 })
.onClick(() => {});
});
}
.padding({ left: 8, right: 8 });
}
.scrollable(ScrollDirection.Horizontal)
.height(190)
.margin({ top: 10 });
Row() {
ForEach(CATEGORIES, (cat: CategoryTab) => {
Text(cat.name)
.fontSize(12)
.fontColor(this.selectedCategory === cat.key ? SP.primary : SP.textSub)
.backgroundColor(this.selectedCategory === cat.key ? SP.primary + '33' : Color.Transparent)
.borderRadius(14)
.padding({ left: 14, right: 14, top: 4, bottom: 4 })
.margin({ left: 6 })
.onClick(() => {
this.selectedCategory = cat.key;
if (cat.key === 'all') {
this.videos = VIDEOS;
} else {
const map: Record<string, string> = {
'movie': '电影',
'tv': '电视剧',
'variety': '综艺'
};
this.videos = VIDEOS.filter(v => v.category === map[cat.key]);
}
});
});
}
.width('100%')
.padding({ left: 12, top: 12, bottom: 8 });
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(this.videos, (v: VideoItem) => {
Column() {
Text(v.cover)
.fontSize(44)
.textAlign(TextAlign.Center)
.width('100%')
.height(90)
.backgroundColor(SP.cardLight)
.borderRadius(6);
Text(v.title)
.fontSize(12)
.fontWeight(FontWeight.Medium)
.fontColor(SP.text)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
.margin({ top: 4 });
Row() {
Text('⭐ ' + formatScore(v.score))
.fontSize(10)
.fontColor(SP.primary);
Column().layoutWeight(1);
if (v.hot) {
Text('🔥')
.fontSize(10);
}
}
.width('100%');
}
.width('47%')
.backgroundColor(SP.card)
.borderRadius(10)
.padding(8)
.margin({ top: 8 })
.onClick(() => {});
});
}
.width('94%')
.padding({ bottom: 16 });
}
.width('100%')
.height('100%')
.alignItems(HorizontalAlign.Start);
}
}
// ---------- 分类内容(电影/电视剧/综艺共用) ----------
@Component struct CategoryContent {
@Prop category: string;
@State videos: VideoItem[] = VIDEOS.filter(v => v.category === this.category);
build() {
Scroll() {
Column() {
Row() {
Text('🔥 热门 ' + this.category)
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(SP.text)
.margin({ left: 12, top: 12 });
}
.width('100%');
Flex({ wrap: FlexWrap.Wrap, justifyContent: FlexAlign.SpaceBetween }) {
ForEach(this.videos, (v: VideoItem) => {
Column() {
Text(v.cover)
.fontSize(48)
.textAlign(TextAlign.Center)
.width('100%')
.height(100)
.backgroundColor(SP.cardLight)
.borderRadius(6);
Text(v.title)
.fontSize(13)
.fontWeight(FontWeight.Medium)
.fontColor(SP.text)
.maxLines(1)
.textOverflow({ overflow: TextOverflow.Ellipsis })
.width('100%')
.margin({ top: 4 });
Row() {
Text('⭐ ' + formatScore(v.score))
.fontSize(10)
.fontColor(SP.primary);
Column().layoutWeight(1);
Text(v.year)
.fontSize(9)
.fontColor(SP.textSub);
}
.width('100%');
}
.width('47%')
.backgroundColor(SP.card)
.borderRadius(10)
.padding(8)
.margin({ top: 8 })
.onClick(() => {});
});
}
.width('94%')
.padding({ bottom: 16 });
}
.width('100%');
}
.scrollable(ScrollDirection.Vertical)
.width('100%')
.height('100%');
}
}
// ---------- 我的页面 ----------
@Component struct MineContent {
build() {
Scroll() {
Column() {
Column() {
Text('👤')
.fontSize(48)
.backgroundColor(SP.cardLight)
.borderRadius(50)
.width(80)
.height(80)
.textAlign(TextAlign.Center);
Text('未登录')
.fontSize(16)
.fontWeight(FontWeight.Bold)
.fontColor(SP.text)
.margin({ top: 8 });
Text('点击登录')
.fontSize(12)
.fontColor(SP.primary)
.margin({ top: 4 });
}
.width('100%')
.padding(20)
.backgroundColor(SP.card)
.borderRadius(12)
.margin({ top: 16 });
Column() {
this.menuItem('📺', '我的收藏');
this.menuItem('⏳', '观看历史');
this.menuItem('💬', '我的评论');
this.menuItem('⚙️', '设置');
}
.width('94%')
.backgroundColor(SP.card)
.borderRadius(12)
.padding({ left: 12, right: 12 })
.margin({ top: 16 });
Text('— 影音有你,精彩无限 —')
.fontSize(10)
.fontColor(SP.textSub)
.margin({ top: 20, bottom: 16 });
}
.width('100%')
.alignItems(HorizontalAlign.Center);
}
.scrollable(ScrollDirection.Vertical)
.width('100%')
.height('100%');
}
@Builder
menuItem(icon: string, label: string) {
Row() {
Text(icon)
.fontSize(18)
.margin({ right: 12 });
Text(label)
.fontSize(14)
.fontColor(SP.text);
Column().layoutWeight(1);
Text('>')
.fontColor(SP.textSub);
}
.width('100%')
.padding({ top: 12, bottom: 12 })
.border({ width: { bottom: 0.5 }, color: SP.border });
}
}
更多推荐




所有评论(0)