OpenHarmony上实现逐帧动画

OpenHarmony上实现逐帧动画

首页休闲益智火柴人像素工艺更新时间:2024-07-30

逐帧动画是常见的一种动画呈现形式,本例就为大家介绍如何通过 translate(),setInterval(),clearAllInterval() 等方法实现逐帧动画。

效果呈现

本例最终效果如上图:

运行环境

本例基于以下环境开发,开发者也可以基于其他适配的版本进行开发:

实现思路

本例的实现有两个关键点:

①将连续走动的火柴人拆分为多帧静态图像,在固定的时间间隔内逐帧将图像移动到动画窗口,间隔时间要小于肉眼可察觉的时间。循环上述动作,就可以实现火柴人的走动动画。

火柴人静态图像如下:

将背景图片以固定速度相对于火柴人走动方向反方向移动,从而实现火柴人向前走动的效果。

背景图如下:

本例使用 translate() 控制火柴人的移动,用 backgroundImagePosition() 控制背景图的移动。

另外,通过 setInterval() 设置火柴人移动的时间间隔,通过 clearAllInterval() 清除移动。

开发步骤

①搭建 UI 框架

使用两个 Row 组件分别呈现背景图和火柴人,第二个 Row 组件作为第一个 Row 组件的子组件,父 Row 组件的背景设置为背景图,子 Row 组件中添加 Image 组件用来呈现火柴人单帧图像。

@Entry @Component export default struct frameAnimation { build() { Column() { // 父Row组件 Row() { // 子Row组件 Row() { // 通过Image组件显示火柴人图像 Image($r("app.media.man")).height(60).width(545.16) }.width(100) .justifyContent(FlexAlign.Start) .alignItems(VerticalAlign.Top) // 截取显示与背景同等大小的区域,控制单个火柴人显示在画面中 .clip(true) } // 添加背景图像 .backgroundImage($r("app.media.background")) // 保持宽高比进行缩小或者放大,使得图片两边都大于或等于显示边界。 .backgroundImageSize(ImageSize.Cover) .width('100%') .height(130) .justifyContent(FlexAlign.Center) .alignItems(VerticalAlign.Bottom) Row() { // 添加跑动按钮 Button('run') .margin({ right: 10 }) .type(ButtonType.Normal) .width(75) .borderRadius(5) // 添加停止按钮 Button('stop') .type(ButtonType.Normal) .borderRadius(5) .width(75) .backgroundColor('#ff0000') }.margin({ top: 30, bottom: 10 }) }.width('100%').width('100%').padding({ top: 30 }) } }

②添加火柴人和背景图片的移动逻辑

通过状态变量设定火柴人和背景图片的位置,位置变化时可以实时刷新 UI 界面。

// 火柴人位置变量 @State manPostion: { x: number, y: number } = { x: 0, y: 0 } // 背景图位置变量 @State treePosition: { x: number, y: number } = { x: 0, y: 0 }

给火柴人和背景图片添加位置属性。

Row() { Row() { Image($r("app.media.man")) .height(60) .width(545.16) // 通过translate实现火柴人的位移。绑定manPosition,用来改变火柴人位置。 .translate(this.manPostion) } ... } .backgroundImage($r("app.media.background")) .backgroundImageSize(ImageSize.Cover) // 通过backgroundImagePosition实现背景图片的位移。绑定treePosition,用来改变背景图片的位置。 .backgroundImagePosition(this.treePosition) ...

③为’‘run’'按钮和"stop"按钮绑定控制逻辑

构建火柴人和背景图片移动的方法,用来设定火柴人和背景图片每次移动的距离。

​这里要注意火柴人每次移动的距离等于两个火柴人之间的间隔距离(像素值)。

// 火柴人移动方法 manWalk() { if (this.manPostion.x <= -517.902) { this.manPostion.x = 0 } else { // 每次移动的距离为火柴人静态图像之间的间隔距离 this.manPostion.x -= 129.69 } } // 背景移动方法 treesMove() { if (this.treePosition.x <= -1215) { this.treePosition.x = 0 } else { this.treePosition.x -= 20 } }

创建 doAnimation() 方法调用上述两个方法,以便在后续的定时器中使用。

doAnimation() { this.manWalk() this.treesMove() }

通过 setInterval 为“run”按钮绑定走动逻辑。

Button('run') .margin({ right: 10 }) .type(ButtonType.Normal) .width(75) .borderRadius(5) .onClick(() => { this.clearAllInterval() // 创建定时器,调用doAnimation方法,启动动画 let timer = setInterval(this.doAnimation.bind(this), 100) this.timerList.push(timer) })

通过 clearAllInterval 为“stop”按钮绑定停止逻辑。

Button('stop') .type(ButtonType.Normal) .borderRadius(5) .width(75) .backgroundColor('#ff0000') .onClick(() => { // 清理定时器,停止动画 this.clearAllInterval() })

完整代码

本例完整代码如下:

@Entry @Component export default struct frameAnimation { // 火柴人位置变量 @State manPostion: { x: number, y: number } = { x: 0, y: 0 } // 背景图位置变量 @State treePosition: { x: number, y: number } = { x: 0, y: 0 } // 定时器列表,当列表清空时,动画停止 private timerList: number[] = [] // 火柴人移动方法 manWalk() { if (this.manPostion.x <= -517.902) { this.manPostion.x = 0 } else { this.manPostion.x -= 129.69 } } // 背景移动方法 treesMove() { if (this.treePosition.x <= -1215) { this.treePosition.x = 0 } else { this.treePosition.x -= 20 } } // 销毁所有定时器 clearAllInterval() { this.timerList.forEach((timer: number) => { clearInterval(timer) }) this.timerList = [] } doAnimation() { this.manWalk() this.treesMove() } build() { Column() { // 父Row组件 Row() { // 子Row组件 Row() { // 通过Image组件显示火柴人图像 Image($r("app.media.man")) .height(60) .width(545.16) // 通过translate实现火柴人的位移。绑定manPosition变量,用来改变火柴人位置。 .translate(this.manPostion) } .width(100) .justifyContent(FlexAlign.Start) .alignItems(VerticalAlign.Top) // 截取显示与背景同等大小的区域,控制单个火柴人显示在画面中 .clip(true) } // 添加背景图像 .backgroundImage($r("app.media.background")) // 保持宽高比进行缩小或者放大,使得图片两边都大于或等于显示边界。 .backgroundImageSize(ImageSize.Cover) // 通过backgroundImagePosition实现背景图片的位移。绑定treePosition,用来改变背景图片的位置。 .backgroundImagePosition(this.treePosition) .width('100%') .height(130) .justifyContent(FlexAlign.Center) .alignItems(VerticalAlign.Bottom) Row() { // 添加跑动按钮 Button('run') .margin({ right: 10 }) .type(ButtonType.Normal) .width(75) .borderRadius(5) .onClick(() => { this.clearAllInterval() let timer = setInterval(this.doAnimation.bind(this), 100) this.timerList.push(timer) }) // 添加停止按钮 Button('stop') .type(ButtonType.Normal) .borderRadius(5) .width(75) .backgroundColor('#ff0000') .onClick(() => { this.clearAllInterval() }) }.margin({ top: 30, bottom: 10 }) }.width('100%').width('100%').padding({ top: 30 }) }

查看全文
大家还看了
也许喜欢
更多游戏

Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved