admin管理员组文章数量:1037775
分享之前使用HarmonyOS NEXT Canvas做的动态GIF视频的一个案例,没有感情,全是技术。
hello,大家好,我是莓创-陈杨。最近忙着改图表组件的 BUG,还有定制化开发一些图表。没啥时间写新东西,草稿里面放了十几个要实现的案例分享,欠的实在太多了,后面再慢慢还吧。这次分享一下之前使用 HarmonyOS NEXT Canvas 做的动态视频的一个案例,没有感情,全是技术。
什么!你还不知道我封装了什么图表组件,我不允许你不知道,还不快去看看:莓创开源图表快速地址
效果
先给大家看一下整体效果
开发准备
开发流程与进度
这次整体开发流程主要如下:
- 获取图片素材列表数据,初始化视频的帧数以及 canvas 画布
- 绘画视频控制器,编写视频按帧数播放的功能
- 动态切换帧数进行播放
- 支持播放词条进行控制播放
- 添加音乐
- 导出视频
目前已经开发完第三步了,后面会继续开发,而且也会继续分享出来。感兴趣的开发可以关注一下。
代码讲解
接下来我简单讲解一下代码,也是需要注意点
1、获取图片素材列表,大家想要生成什么 GIF 或者视频就去找什么素材。可以用第三方的链接,可以用 base64,可以用本地项目图片。最后都通过 ImageBitmap 方法将图片存储为 canvas 渲染的像素数据。非常方面,在 H5 还要担心跨域之类的问题。
代码语言:javascript代码运行次数:0运行复制let playTimer: number = 0@Entry@Componentexport struct VideoEditing { private settings: RenderingContextSettings = new RenderingContextSettings(true) private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings) private scroller: Scroller = new Scroller() @State w: number = 400; // 编程画板的宽度 @State h: number = 760; // 编程画板的尺寸 @State levelList: any[] = [ new ImageBitmap('common/images/icon0.png'), new ImageBitmap('common/images/icon1.png'), new ImageBitmap('common/images/icon2.png'), new ImageBitmap('common/images/icon3.png'), new ImageBitmap('common/images/icon4.png'), new ImageBitmap('common/images/icon5.png'), new ImageBitmap('common/images/icon6.png'), new ImageBitmap('common/images/icon7.png') .... ];
build() { Flex({direction: FlexDirection.Column, alignItems: ItemAlign.Start}) { Column() { Canvas(this.context) .onReady(() => { }) .width(this.w) .height(this.h) .backgroundColor('#fff') }.justifyContent(FlexAlign.Center).clip(true).width('100%').flexGrow(1).backgroundColor('#f8f8f8') } }}
2、绘画视频控制器主体,这里主要控制变量就是视频的帧数 fps,再结合循环计时器形成一个小型播放器,循环器的时间规则就是 1000 / fps,代表着每秒几帧,想快就放大 fps,想慢就缩小 fps,是不是很简单。
代码语言:javascript代码运行次数:0运行复制let playTimer: number = 0@Entry@Componentexport struct VideoEditing { private settings: RenderingContextSettings = new RenderingContextSettings(true) private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings) private scroller: Scroller = new Scroller() @State w: number = 400; // 编程画板的宽度 @State h: number = 760; // 编程画板的尺寸 @State levelList: any[] = [ .... ]; // 图层list @State ispause: boolean = true // 是否是暂停状态 @State plusNum: number = 0 // 帧总量 @State plusCount: number = 0 // 帧总量计数器(判断循环次数) @State count: number = 0 // 当前帧 @State fps: number = 25 // 25帧/秒 @State fpsNumber: number = 2 // 25帧/秒 @State recordFrom: number = 0 // 记录起始帧 @State recordTo: number = 0 // 记录结束帧 @State imgsLen: number = 0 // 记录帧长度
// 跳到某一帧 goto (n: number) { this.count = n this.drawImg(this.levelList[n]) } drawImg(img) { // const image = offCanvas.transferToImageBitmap() // this.context.transferFromImageBitmap(image) this.context.drawImage(img,0,0,this.w, this.h) } fromTo(from: number, to: number) { const self = this const fps = this.fps // 先清除上次未执行完的动画 clearInterval(playTimer) const timeFn = (): undefined => { if (self.ispause) { return } // 当总量计数器达到帧总量的时候退出 if (self.plusNum <= self.plusCount) { self.resetData() // clearInterval(playTimer) return } else { // 未达到,继续循环 // 帧计数器 self.count++ // 一次循环结束,重置keyCount为from if (self.count > to) { self.count = from } this.scroller.scrollTo({ xOffset: self.count * 150, yOffset: 0 }) self.goto(self.count) // 总量计数器 self.plusCount++ return } } // 总量计数器 this.plusCount = 0
// 帧总量 帧数*循环次数first this.plusNum = to - from + 1 this.ispause = false
this.recordFrom = from this.recordTo = to
timeFn() playTimer = setInterval(timeFn, 1000 / fps) } // 重置数据 停止并回到第一帧或cover帧 resetData() { this.ispause = true clearInterval(playTimer) this.plusNum = 0 this.plusCount = 0 this.scroller.scrollTo({ xOffset: 0, yOffset: 0 }) // 重置记录 this.recordFrom = 0 this.recordTo = this.imgsLen - 1 this.count = 0 } build() { Flex({direction: FlexDirection.Column, alignItems: ItemAlign.Start}) { Column() { Canvas(this.context) .onReady(() => { this.goto(0) }) .width(this.w) .height(this.h) .backgroundColor('#fff') }.justifyContent(FlexAlign.Center).clip(true).width('100%').flexGrow(1).backgroundColor('#f8f8f8') Column() { Flex({justifyContent: FlexAlign.SpaceBetween}) { Row() { Text(String(this.fps)).fontSize(15).margin({right: 4}) Text('帧/秒').fontSize(12) Image($r('app.media.ic_public_spinner_small')).width(20) }.onClick(() => { // this.showSex = true TextPickerDialog.show({ range: ['1', '12', '25', '30', '50', '60'], selected: this.fpsNumber, // selectedTextStyle: {color:'rgba(255, 80, 121, 1)'}, onAccept: (value: TextPickerResult) => { this.fpsNumber = Number(value.index) this.fps = Number(value.value) } }) }) Row() { Image($r('app.media.ic_public_play')).width(20) .onClick(() => { this.imgsLen = this.levelList.length this.recordFrom = 0 this.recordTo = this.imgsLen - 1 this.fromTo(this.recordFrom, this.recordTo) }) Image($r('app.media.ic_public_pause')).width(20) } Row() { Image($r('app.media.ic_public_music_filled')).width(20) } }.padding({top: 20, bottom: 20, left: 10, right: 10}) } }.position({x: 0, y: 0}).width('100%').height('100%').backgroundColor('#fff').transition({ type: TransitionType.Insert, translate: { x: 0, y: '100%' } }).transition({ type: TransitionType.Delete, translate: { x: 0, y: '100%' } }) }}
以上就是前三步的实现代码,这三个步骤整体并不难。在页面能够实现简单的播放之后,后面就是生成视频或者 GIF 了。其实还有一个功能也很重要,就是导入视频,解析视频,然后就可以做视频编辑器了,这个也是一个大工程,想玩的可以去尝试尝试
分享之前使用HarmonyOS NEXT Canvas做的动态GIF视频的一个案例,没有感情,全是技术。
hello,大家好,我是莓创-陈杨。最近忙着改图表组件的 BUG,还有定制化开发一些图表。没啥时间写新东西,草稿里面放了十几个要实现的案例分享,欠的实在太多了,后面再慢慢还吧。这次分享一下之前使用 HarmonyOS NEXT Canvas 做的动态视频的一个案例,没有感情,全是技术。
什么!你还不知道我封装了什么图表组件,我不允许你不知道,还不快去看看:莓创开源图表快速地址
效果
先给大家看一下整体效果
开发准备
开发流程与进度
这次整体开发流程主要如下:
- 获取图片素材列表数据,初始化视频的帧数以及 canvas 画布
- 绘画视频控制器,编写视频按帧数播放的功能
- 动态切换帧数进行播放
- 支持播放词条进行控制播放
- 添加音乐
- 导出视频
目前已经开发完第三步了,后面会继续开发,而且也会继续分享出来。感兴趣的开发可以关注一下。
代码讲解
接下来我简单讲解一下代码,也是需要注意点
1、获取图片素材列表,大家想要生成什么 GIF 或者视频就去找什么素材。可以用第三方的链接,可以用 base64,可以用本地项目图片。最后都通过 ImageBitmap 方法将图片存储为 canvas 渲染的像素数据。非常方面,在 H5 还要担心跨域之类的问题。
代码语言:javascript代码运行次数:0运行复制let playTimer: number = 0@Entry@Componentexport struct VideoEditing { private settings: RenderingContextSettings = new RenderingContextSettings(true) private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings) private scroller: Scroller = new Scroller() @State w: number = 400; // 编程画板的宽度 @State h: number = 760; // 编程画板的尺寸 @State levelList: any[] = [ new ImageBitmap('common/images/icon0.png'), new ImageBitmap('common/images/icon1.png'), new ImageBitmap('common/images/icon2.png'), new ImageBitmap('common/images/icon3.png'), new ImageBitmap('common/images/icon4.png'), new ImageBitmap('common/images/icon5.png'), new ImageBitmap('common/images/icon6.png'), new ImageBitmap('common/images/icon7.png') .... ];
build() { Flex({direction: FlexDirection.Column, alignItems: ItemAlign.Start}) { Column() { Canvas(this.context) .onReady(() => { }) .width(this.w) .height(this.h) .backgroundColor('#fff') }.justifyContent(FlexAlign.Center).clip(true).width('100%').flexGrow(1).backgroundColor('#f8f8f8') } }}
2、绘画视频控制器主体,这里主要控制变量就是视频的帧数 fps,再结合循环计时器形成一个小型播放器,循环器的时间规则就是 1000 / fps,代表着每秒几帧,想快就放大 fps,想慢就缩小 fps,是不是很简单。
代码语言:javascript代码运行次数:0运行复制let playTimer: number = 0@Entry@Componentexport struct VideoEditing { private settings: RenderingContextSettings = new RenderingContextSettings(true) private context: CanvasRenderingContext2D = new CanvasRenderingContext2D(this.settings) private scroller: Scroller = new Scroller() @State w: number = 400; // 编程画板的宽度 @State h: number = 760; // 编程画板的尺寸 @State levelList: any[] = [ .... ]; // 图层list @State ispause: boolean = true // 是否是暂停状态 @State plusNum: number = 0 // 帧总量 @State plusCount: number = 0 // 帧总量计数器(判断循环次数) @State count: number = 0 // 当前帧 @State fps: number = 25 // 25帧/秒 @State fpsNumber: number = 2 // 25帧/秒 @State recordFrom: number = 0 // 记录起始帧 @State recordTo: number = 0 // 记录结束帧 @State imgsLen: number = 0 // 记录帧长度
// 跳到某一帧 goto (n: number) { this.count = n this.drawImg(this.levelList[n]) } drawImg(img) { // const image = offCanvas.transferToImageBitmap() // this.context.transferFromImageBitmap(image) this.context.drawImage(img,0,0,this.w, this.h) } fromTo(from: number, to: number) { const self = this const fps = this.fps // 先清除上次未执行完的动画 clearInterval(playTimer) const timeFn = (): undefined => { if (self.ispause) { return } // 当总量计数器达到帧总量的时候退出 if (self.plusNum <= self.plusCount) { self.resetData() // clearInterval(playTimer) return } else { // 未达到,继续循环 // 帧计数器 self.count++ // 一次循环结束,重置keyCount为from if (self.count > to) { self.count = from } this.scroller.scrollTo({ xOffset: self.count * 150, yOffset: 0 }) self.goto(self.count) // 总量计数器 self.plusCount++ return } } // 总量计数器 this.plusCount = 0
// 帧总量 帧数*循环次数first this.plusNum = to - from + 1 this.ispause = false
this.recordFrom = from this.recordTo = to
timeFn() playTimer = setInterval(timeFn, 1000 / fps) } // 重置数据 停止并回到第一帧或cover帧 resetData() { this.ispause = true clearInterval(playTimer) this.plusNum = 0 this.plusCount = 0 this.scroller.scrollTo({ xOffset: 0, yOffset: 0 }) // 重置记录 this.recordFrom = 0 this.recordTo = this.imgsLen - 1 this.count = 0 } build() { Flex({direction: FlexDirection.Column, alignItems: ItemAlign.Start}) { Column() { Canvas(this.context) .onReady(() => { this.goto(0) }) .width(this.w) .height(this.h) .backgroundColor('#fff') }.justifyContent(FlexAlign.Center).clip(true).width('100%').flexGrow(1).backgroundColor('#f8f8f8') Column() { Flex({justifyContent: FlexAlign.SpaceBetween}) { Row() { Text(String(this.fps)).fontSize(15).margin({right: 4}) Text('帧/秒').fontSize(12) Image($r('app.media.ic_public_spinner_small')).width(20) }.onClick(() => { // this.showSex = true TextPickerDialog.show({ range: ['1', '12', '25', '30', '50', '60'], selected: this.fpsNumber, // selectedTextStyle: {color:'rgba(255, 80, 121, 1)'}, onAccept: (value: TextPickerResult) => { this.fpsNumber = Number(value.index) this.fps = Number(value.value) } }) }) Row() { Image($r('app.media.ic_public_play')).width(20) .onClick(() => { this.imgsLen = this.levelList.length this.recordFrom = 0 this.recordTo = this.imgsLen - 1 this.fromTo(this.recordFrom, this.recordTo) }) Image($r('app.media.ic_public_pause')).width(20) } Row() { Image($r('app.media.ic_public_music_filled')).width(20) } }.padding({top: 20, bottom: 20, left: 10, right: 10}) } }.position({x: 0, y: 0}).width('100%').height('100%').backgroundColor('#fff').transition({ type: TransitionType.Insert, translate: { x: 0, y: '100%' } }).transition({ type: TransitionType.Delete, translate: { x: 0, y: '100%' } }) }}
以上就是前三步的实现代码,这三个步骤整体并不难。在页面能够实现简单的播放之后,后面就是生成视频或者 GIF 了。其实还有一个功能也很重要,就是导入视频,解析视频,然后就可以做视频编辑器了,这个也是一个大工程,想玩的可以去尝试尝试
本文标签: 分享之前使用HarmonyOS NEXT Canvas做的动态GIF视频的一个案例,没有感情,全是技术
版权声明:本文标题:分享之前使用HarmonyOS NEXT Canvas做的动态GIF视频的一个案例,没有感情,全是技术。 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://it.en369.cn/jiaocheng/1748298948a2281926.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论