在HarmonyOS NEXT中,ArkUI是一个非常强大的UI框架,能够帮助开发者快速构建出美观且功能丰富的用户界面。本文将详细介绍如何使用ArkUI实现一个影视App的视频播放页面。将从零开始,逐步构建一个功能完善的视频播放页面,并解释每一部分的代码实现。
1. 项目结构
在开始之前,先来看一下项目的结构。我的项目结构如下:
src/
├── common/
│ ├── bean/
│ │ └── ApiTypes.ts
│ └── dialog/
│ └── EpisodeDialogView.ts
├── pages/
│ └── VideoPlayerPage.ts
ApiTypes.ts
定义了视频数据的类型。EpisodeDialogView.ts
是一个自定义的剧集选择对话框组件。VideoPlayerPage.ts
是视频播放页面的主文件。
2. 视频播放页面的实现
2.1 引入必要的模块
首先,需要引入一些必要的模块和组件:
typescript
import { VideoItem } from "../../common/bean/ApiTypes";
import { window } from "@kit.ArkUI";
import { common } from "@kit.AbilityKit";
import { EpisodeDialogView } from "../../common/dialog/EpisodeDialogView";
VideoItem
是定义的一个视频数据类型的接口。window
和common
是HarmonyOS提供的系统模块,用于控制窗口和UI能力。EpisodeDialogView
是自定义的剧集选择对话框组件。
2.2 构建视频播放页面
使用 @Builder
装饰器来构建视频播放页面:
typescript
@Builder
export function VideoPlayerPageBuilder() {
VideoPlayer()
}
VideoPlayerPageBuilder
是一个构建函数,它调用了 VideoPlayer
组件来构建整个页面。
2.3 视频播放组件
接下来,定义 VideoPlayer
组件:
typescript
@Component
struct VideoPlayer {
@State title: string = '';
private controller: VideoController | undefined;
@State previewUri: Resource = $r('app.media.play_circle_fill');
@State videoSrc: string = 'http://staticvip.iyuba.cn/video/small/202412/1009544_c.mp4'; // 使用时请替换为实际视频加载网址
@State tvUrls?: string[] = [];
@State isTv: boolean = false;
@State tvIndex: number = 0;
private description: string = '';
private rawTitle: string = '';
private isToggle = false;
@State toggleText: string = '';
@State toggleBtn: string = '展开';
@State
是ArkUI中的状态管理装饰器,用于管理组件的状态。controller
是视频播放器的控制器,用于控制视频的播放、暂停等操作。previewUri
是视频的预览图资源。videoSrc
是视频的播放地址。tvUrls
是电视剧的剧集列表。isTv
用于判断当前播放的是否是电视剧。tvIndex
是当前播放的剧集索引。description
是视频的简介。rawTitle
是视频的原始标题。isToggle
用于控制简介的展开和收起状态。toggleText
是简介的显示文本。toggleBtn
是简介展开/收起按钮的文本。
2.4 页面生命周期
在 aboutToAppear
生命周期函数中,可以进行一些初始化操作:
typescript
aboutToAppear() {
// 初始化操作
}
2.5 屏幕方向切换
定义了一个 changeOrientation
方法,用于切换屏幕方向:
typescript
private changeOrientation(isLandscape: boolean) {
let context = getContext(this) as common.UIAbilityContext;
window.getLastWindow(context).then((lastWindow) => {
lastWindow.setPreferredOrientation(isLandscape ? window.Orientation.LANDSCAPE : window.Orientation.PORTRAIT);
});
}
getContext
用于获取当前组件的上下文。window.getLastWindow
获取当前窗口,并设置其方向为横屏或竖屏。
2.6 剧集选择对话框
定义了一个剧集选择对话框,并为其设置了回调函数:
typescript
episodeDialogController: CustomDialogController = new CustomDialogController({
builder: EpisodeDialogView({
current: this.tvIndex,
tvUrls: this.tvUrls,
clickCallback: (item: string, index: number) => { this.onDialogClickCallback(item, index) }
}),
alignment: DialogAlignment.Bottom,
offset: { dx: 0, dy: -30 },
customStyle: true
});
private onDialogClickCallback(item: string, index: number) {
this.videoSrc = item;
this.tvIndex = index;
this.title = this.rawTitle + ' 第' + (index + 1) + '集';
this.episodeDialogController.close();
this.controller?.start();
}
CustomDialogController
是自定义对话框的控制器。EpisodeDialogView
是剧集选择对话框的视图组件。onDialogClickCallback
是剧集选择后的回调函数,用于更新视频播放地址和标题。
剧集选择框的实现:
typescript
/**
* @author: 猫哥
* @date: 2025/1/15 0:30
* @description:
* @version:
*/
/**
* 电视剧集数选择对话框
*/
@Preview
@CustomDialog
export struct EpisodeDialogView {
controller: CustomDialogController
@Prop current: number
@Prop tvUrls?:string[] = []
//确认按钮回调
clickCallback?: (item:string,idx:number) => void
// 添加一个方法来获取按钮的背景颜色
private getButtonColor(idx: number): Color {
if (this.current === idx) {
return Color.Blue // 当前选中的按钮背景色为蓝色
} else {
return Color.White // 其他按钮背景色为白色
}
}
build() {
Column({space: 10}){
Grid() {
ForEach(this.tvUrls, (item: string,idx) => {
GridItem() {
Button('第'+(idx+1)+'集', { buttonStyle: ButtonStyleMode.NORMAL, role: ButtonRole.NORMAL })
.borderRadius(5)
.fontColor('#fffab52a')
.width(80)
.height(40)
.padding(5)
.backgroundColor(this.getButtonColor(idx))
.onClick(() => {
this.clickCallback?.(item,idx)
})
}
}, (item: string) => item.toString())
}
.width('100%')
.scrollBar(BarState.Off)
.columnsTemplate('1fr 1fr 1fr 1fr')
.columnsGap(4)
.rowsGap(4)
.height(300)
}.width('98%').height('40%')
.backgroundColor(Color.White)
.padding(20).borderRadius(20)
}
}
2.7 按钮背景颜色
定义了一个方法来获取按钮的背景颜色:
typescript
private getButtonColor(idx: number): Color {
if (this.tvIndex === idx) {
return Color.Blue; // 当前选中的按钮背景色为蓝色
} else {
return Color.White; // 其他按钮背景色为白色
}
}
2.8 构建页面布局
最后,使用 build
方法来构建页面的布局:
typescript
build() {
NavDestination() {
Column() {
Row() {
Stack() {
Video({
src: this.videoSrc,
previewUri: this.previewUri,
controller: this.controller
})
.width('100%')
.muted(false) //设置是否静音
.controls(true) //设置是否显示默认控制条
.autoPlay(true) //设置是否自动播放
.loop(false) //设置是否循环播放
.objectFit(ImageFit.Contain) //设置视频适配模式
.onError(() => { //失败事件回调
console.info("Video error.");
})
.onFullscreenChange(event => {
if (event.fullscreen) {
this.changeOrientation(true);
} else {
this.changeOrientation(false);
}
})
.zIndex(1);
Text(this.title).fontColor(Color.White).width('100%').padding(5)
.alignSelf(ItemAlign.Start).margin({ bottom: 280 }).zIndex(2);
}
}.width('100%').height('40%');
Column() {
Text('简介').fontSize(18).padding({ bottom: 10 }).fontWeight(FontWeight.Bold).alignSelf(ItemAlign.Start);
Text(this.toggleText).fontSize(14).lineHeight(20).alignSelf(ItemAlign.Start);
Text(this.toggleBtn).fontSize(14).fontColor(Color.Gray).padding(10).alignSelf(ItemAlign.End).onClick(() => {
this.isToggle = !this.isToggle;
if (this.isToggle) {
this.toggleBtn = '收起';
this.toggleText = this.description;
} else {
this.toggleBtn = '展开';
this.toggleText = this.description.substring(0, 100) + '...';
}
});
}.padding(10);
if (this.isTv) {
Flex({
direction: FlexDirection.Row,
justifyContent: FlexAlign.SpaceBetween,
alignContent: FlexAlign.SpaceBetween
}) {
Text('选集').fontSize(18).fontWeight(FontWeight.Bold);
SymbolGlyph($r('sys.symbol.more'))
.fontWeight(FontWeight.Lighter)
.fontSize(32)
.fontColor(['#fffab52a'])
.onClick(() => {
this.episodeDialogController.open();
});
}.padding(10);
Scroll() {
Row({ space: 10 }) {
ForEach(this.tvUrls, (item: string, idx) => {
Button('第' + (idx + 1) + '集', { buttonStyle: ButtonStyleMode.NORMAL, role: ButtonRole.NORMAL })
.borderRadius(5)
.fontColor('#fffab52a')
.width(80)
.height(40)
.padding(5)
.backgroundColor(this.getButtonColor(idx))
.onClick(() => {
this.videoSrc = item;
this.tvIndex = idx;
this.title = this.rawTitle + ' 第' + (idx + 1) + '集';
this.controller?.start();
});
});
}.alignItems(VerticalAlign.Center).padding(10);
}.scrollable(ScrollDirection.Horizontal);
}
}.width('100%');
}
.width("100%")
.height("100%")
.onReady(ctx => {
interface params {
item: VideoItem;
}
let par = ctx.pathInfo.param as params;
this.videoSrc = par.item.video;
this.rawTitle = par.item.title;
this.title = this.rawTitle;
this.description = par.item.desc;
this.tvUrls = par.item.tvurls;
if (this.tvUrls?.length ?? 0 > 0) {
this.isTv = true;
}
this.tvIndex = 0;
this.toggleText = this.description.substring(0, 100) + '...';
})
.onShown(() => {
console.info('VideoPlayer onShown');
});
}
NavDestination
是导航目的地组件,用于定义页面的导航行为。Column
和Row
是布局组件,用于构建页面的布局。Video
是视频播放组件,用于播放视频。Text
是文本组件,用于显示标题和简介。Button
是按钮组件,用于选择剧集。Scroll
是滚动组件,用于实现剧集列表的横向滚动。
3. 总结
通过以上步骤,实现了一个功能完善的视频播放页面。这个页面不仅支持视频的播放、暂停、全屏等基本功能,还支持电视剧的剧集选择和简介的展开/收起。希望这篇文章能够帮助你更好地理解如何使用HarmonyOS NEXT和ArkUI来构建一个影视App的视频播放页面。
如果你有任何问题或建议,欢迎在评论区留言!
作者介绍
作者:csdn猫哥
原文链接:https://blog.csdn.net/yyz_1987
团队介绍
坚果派团队由坚果等人创建,团队拥有12个华为HDE带领热爱HarmonyOS/OpenHarmony的开发者,以及若干其他领域的三十余位万粉博主运营。专注于分享HarmonyOS/OpenHarmony、ArkUI-X、元服务、仓颉等相关内容,团队成员聚集在北京、上海、南京、深圳、广州、宁夏等地,目前已开发鸿蒙原生应用和三方库60+,欢迎交流。
版权声明
本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。