HarmonyOS鸿蒙学习笔记(15)Swiper实现抖音切换视频播放效果
Swiper实战
- 1、项目结构
- 2、PageVideo和PlayView简单说明
- 参考资料:
1、项目结构
前面写了Swiper控件的初步使用,本文结合@Link ,@State、@Watch来实现类似抖音滑动播放视频的效果。本文源码地址Swiper实战。
项目结构如下图:
其中PageVideo.ets
是APP首页,用来提供视频播放的列表。PlayView.ets
是视频播放器,随着PageVideo.ets
的滚动切换视频,来通知PlayView.ets
暂停和开始视频的播放。VideoItem.ets包含着视频信息,比如视频播放地址等。
2、PageVideo和PlayView简单说明
2.1 @State变量的使用
PageVideo
提供了两个@State
修饰的变量:pageShow
和number
,这两个状态发生改变的时候会通知PlayView
对播放器进行调整。
@Entry@Componentexport struct PageVideo { @State videoArray: Array<VideoItem> = initializeOnStartup() // 数据源 //当切换视频的时候,会通知PlayView播放当前的视频,同时关闭上一个视频 @State index: number = 0 // 当前滑动的索引位置 @State pageShow: boolean = false // 当前页面是否可见,仅对@Entry修饰的主页面PageVideo而言,子组件需要用@Watch监听该状态}
2.2 @Link和@Watch变量的使用
那么PlayView
是如何感知这两个状态发生了改变了呢?此时@Link
标签就起到了作用.PlayView
代码如下:
@Componentexport struct PlayView { // 四条控制页面可见、页面不可见的控制 private isShow: boolean = false // 是否是可见状态 @Link @Watch("needPageShow") index: number // 监听父组件索引index状态变化, 不能在组件内部进行初始化 @Link @Watch("needPageShow") pageShow: boolean // 监听父组件是否可见pageShow状态变化, 不能在组件内部进行初始化 }
其中也是用了@Watch标签定义了needPageShow
的方法,当number和pageShow的值发生变化的时候会调用该方法,用来控制视频暂停上一个和播放当前的视频:
// 监听父组件index、pageShow属性变化就会触发的方法,@Watch needPageShow() { console.log("playView*** needPageShow") if (this.pageShow) { // 页面可见时触发 if (this.position == this.index) { // 判断index与当前所在位置是否相同 this.isShow = true; this.onPageShow() } else { if (this.isShow) { // 已经是可见的状态改为不可见,并触发不可见方法回调 this.isShow = false; this.onPageSwiperHide() } } } else { // 页面不可见触发 if (this.position == this.index) { if (this.isShow) { // 已经是可见的状态改为不可见,并触发不可见方法回调 this.isShow = false; this.onPageHide() } } } }
2.3、Swiper的使用和PlayView的初始化
在PlayVideo中根据视频的个数初始化了Swiper
控件,该控件的子控件就是PlayView
,注意在初始化PlayView时,初始化@Link修饰的变量时需要用$修饰。
build() { Column() { Swiper() { ForEach(this.videoArray.map((item, index) => { return { i: index, data: item }; }), item => { //初始化@Link修饰的变量时需要用$修饰。 PlayView({ index: $index, pageShow: $pageShow, item: item.data, position: item.i }) }, item => item.data.id.toString()) } .indicator(false) // 默认开启指示点 .loop(false) // 默认开启循环播放 .vertical(true) // 默认横向切换、更改为竖向滑动 .onChange((index: number) => {//滚动Swiper的时候用来监听页面的变化 //此处会触发PlayView的needPageShow方法。 this.index = index }) } }
2.4、页面可见状态发生改变时对视频进行暂停和播放
我们按home键的时候,APP进入后台。也就是在APP前后台状态发生改变的时候,需要通知PlayView
进行暂停或者播放。在PlayVideo里面实现了onPageShow
和onPageHide
,在这两个方法里面改变pageShow
的值,当pageShow
的值发生改变时,PlayView
因为@Link
的作用会监听到值的改变,从而执行PlayView
的needPageShow
方法用来控制视频的暂停和播放。
// 当此页面可见时触发,仅@Entry修饰的自定义组件生效 onPageShow(): void { //当pageShow的值发生改变时,PlayView因为@Link的作用会监听到值的改变,从而 this.pageShow = true; } // 当此页面不可见时触发,仅@Entry修饰的自定义组件生效 onPageHide(): void { this.pageShow = false; }
2.5 PlayView和PageVidew源码:
二者全部的源码如下,当然读者可以通过Swiper实战下载整个项目来分析和学习。
import {VideoItem} from '../play/VideoItem'@Componentexport struct PlayView { // 四条控制页面可见、页面不可见的控制 private isShow: boolean = false // 是否是可见状态 @Link @Watch("needPageShow") index: number // 监听父组件索引index状态变化, 不能在组件内部进行初始化 @Link @Watch("needPageShow") pageShow: boolean // 监听父组件是否可见pageShow状态变化, 不能在组件内部进行初始化 private position: number // 当前页面所在位置 @ObjectLink private item: VideoItem // @Observed与@ObjectLink配合使用实现 class修饰的模型数据改变触发UI更新 @State private playState: number = 0 // 0表示停止播放--stop 1表示开始播放--start 2表示暂停播放--pause // @ts-ignore private videoController: VideoController = new VideoController() // 视频播放器控制器 build() { Stack({ alignContent: Alignment.Center | Alignment.End }) { Video({ src: this.item.src, // 视频播放地址 controller: this.videoController // 视频播放器控制器 }) .controls(false) // 不需要控制栏 .autoPlay(this.playState == 1 ? true : false) // 首次可见状态自动播放 .objectFit(ImageFit.Contain) // 视频窗口自适应视频大小 .loop(true) // 循环播放 .onClick(() => { // 点击播放、再点击暂停播放 if (this.playState == 1) { this.playState = 2; this.videoController.pause(); } else if (this.playState == 2) { this.playState = 1; this.videoController.start(); } }) Column() { Image(this.item.isLikes ? $r('app.media.vote1') : $r('app.media.vote0')) // 点赞图标 .width(36).height(36) .onClick(() => { if (this.item.isLikes) {this.item.likesCount--; } else {this.item.likesCount++; } this.item.isLikes = !this.item.isLikes; }).margin({ top: 40 }) Text(this.item.likesCount == 0 ? '点赞' : ('' + this.item.likesCount)).fontSize(16).fontColor(0xffffff) // 评论图标 Image($r('app.media.comment')) .width(36).height(36).margin({ top: 20 }) Text(this.item.commentCount == 0 ? '评论' : ('' + this.item.commentCount)) .fontSize(16) .fontColor(0xffffff) // 转发图标 Image($r('app.media.share')) .width(36).height(36).margin({ top: 20 }) }.offset({ x: '-5%', y: '-10%' }) // 位置调整 Text(this.item.title) .fontSize(16) .fontColor(0xffffff) .margin(10) .offset({ x: '-50%', y: '40%' }) }.backgroundColor(Color.Black) .width('100%') .height('100%') } // 自定义的方法。页面可见状态会被调用,多次调用 onPageShow(): void { console.log("pageView*** OnPageShow") if (this.playState != 1) { this.playState = 1; this.videoController.start(); } } // 自定义的方法。页面不可见状态会被调用,多次调用,这种不可见是Swiper滑动时触发的 private onPageSwiperHide(): void { console.log("playView*** onPageSwiperHide") if (this.playState != 0) { this.playState = 0; this.videoController.stop(); // 停止视频播放 } } // 自定义的方法。页面不可见状态会被调用,多次调用,这种不可见是点击页面跳转、或者应用回到桌面时触发的 onPageHide(): void { console.log("playView*** onPageHide") if (this.playState != 2) { this.playState = 2; this.videoController.pause(); // 暂停视频播放 } } // 监听父组件index、pageShow属性变化就会触发的方法,@Watch needPageShow() { console.log("playView*** needPageShow") if (this.pageShow) { // 页面可见时触发 if (this.position == this.index) { // 判断index与当前所在位置是否相同 this.isShow = true; this.onPageShow() } else { if (this.isShow) { // 已经是可见的状态改为不可见,并触发不可见方法回调 this.isShow = false; this.onPageSwiperHide() } } } else { // 页面不可见触发 if (this.position == this.index) { if (this.isShow) { // 已经是可见的状态改为不可见,并触发不可见方法回调 this.isShow = false; this.onPageHide() } } } }}import { VideoItem, initializeOnStartup } from '../play/VideoItem'import { PlayView } from '../play/PlayView'@Entry@Componentexport struct PageVideo { @State videoArray: Array<VideoItem> = initializeOnStartup() // 数据源 //当切换视频的时候,会通知PlayView播放当前的视频,同时关闭上一个视频 @State index: number = 0 // 当前滑动的索引位置 @State pageShow: boolean = false // 当前页面是否可见,仅对@Entry修饰的主页面PageVideo而言,子组件需要用@Watch监听该状态 build() { Column() { Swiper() { ForEach(this.videoArray.map((item, index) => { return { i: index, data: item }; }), item => { PlayView({ index: $index, pageShow: $pageShow, item: item.data, position: item.i }) }, item => item.data.id.toString()) } .indicator(false) // 默认开启指示点 .loop(false) // 默认开启循环播放 .vertical(true) // 默认横向切换、更改为竖向滑动 .onChange((index: number) => { this.index = index }) } } // 当此页面可见时触发,仅@Entry修饰的自定义组件生效 onPageShow(): void { this.pageShow = true; } // 当此页面不可见时触发,仅@Entry修饰的自定义组件生效 onPageHide(): void { this.pageShow = false; }}
参考资料:
鸿蒙实战项目表,分享知识与见解,一起探索HarmonyOS的独特魅力。https://gitee.com/harmonyos/codelabs
HarmonyOS鸿蒙学习笔记(12)@Link的作用
HarmonyOS鸿蒙学习笔记(8)Swiper实现轮播滚动效果
HarmonyOS鸿蒙学习笔记(5)@State作用说明和简单案例
Swiper实战
@Watch标签的作用