如何实现逐帧动画? 精华

妮巴~😘 显示全部楼层 发表于 2023-8-30 09:37:18

【问题描述】想实现一个逐帧动画,点击“run”按钮,开始运动,点击“stop”按钮,运动停止。在官网上看到动画和旋转属性,自己尝试了一下,没有达到想要的效果, 求大佬提供一个demo?

【运行环境】硬件:rk3568; ROM: 3.2 Beta5; API 9

您尚未登录,无法参与评论,登录后可以:
参与开源共建问题交流
认同或收藏高质量问答
获取积分成为开源共建先驱

精彩评论1

hyacinth养花人

沙发 发表于 2023-8-30 14:41:45

这是我写的一个demo,可以参考一下:
实现效果
● 点击“run”按钮,火柴人开始走动。
● 点击“stop”按钮,火柴人停止走动。
image.gif

实现思路
本例的实现有两个关键点:
● 将连续走动的火柴人拆分为多帧静态图像,在固定的时间间隔内逐帧将图像移动到动画窗口,间隔时间要小于肉眼可察觉的时间。循环上述动作,就可以实现火柴人的走动动画。 火柴人静态图像如下:
image (23).png

● 将背景图片以固定速度相对于火柴人走动方向反方向移动,从而实现火柴人向前走动的效果。 背景图如下:
image (24).png

本例使用translate()控制火柴人的移动,用backgroundImagePosition()控制背景图的移动。另外,通过setInterval()设置火柴人移动的时间间隔,通过clearAllInterval()清除移动。


开发步骤
1.  搭建UI框架。
使用两个Row组件分别呈现背景图和火柴人,第二个Row组件作为第一个Row组件的子组件,父Row组件的背景设置为背景图,子Row组件中添加Image组件用来呈现火柴人单帧图像
  1. @Entry
  2. @Component
  3. export default struct frameAnimation {
  4.   build() {
  5.     Column() {
  6.       // 父Row组件
  7.       Row() {
  8.         // 子Row组件
  9.         Row() {
  10.           // 通过Image组件显示火柴人图像
  11.           Image($r("app.media.man")).height(60).width(545.16)
  12.         }.width(100)
  13.         .justifyContent(FlexAlign.Start)
  14.         .alignItems(VerticalAlign.Top)
  15.         // 截取显示与背景同等大小的区域,控制单个火柴人显示在画面中
  16.         .clip(true)
  17.       }
  18.       // 添加背景图像
  19.       .backgroundImage($r("app.media.background"))
  20.       // 保持宽高比进行缩小或者放大,使得图片两边都大于或等于显示边界。
  21.       .backgroundImageSize(ImageSize.Cover)
  22.       .width('100%')
  23.       .height(130)
  24.       .justifyContent(FlexAlign.Center)
  25.       .alignItems(VerticalAlign.Bottom)
  26.       Row() {
  27.         // 添加跑动按钮
  28.         Button('run')
  29.           .margin({ right: 10 })
  30.           .type(ButtonType.Normal)
  31.           .width(75)
  32.           .borderRadius(5)
  33.         // 添加停止按钮
  34.         Button('stop')
  35.           .type(ButtonType.Normal)
  36.           .borderRadius(5)
  37.           .width(75)
  38.           .backgroundColor('#ff0000')
  39.       }.margin({ top: 30, bottom: 10 })
  40.     }.width('100%').width('100%').padding({ top: 30 })
  41.   }
  42. }
复制代码

2. 添加火柴人和背景图片的移动逻辑。
通过状态变量设定火柴人和背景图片的位置,位置变化时可以实时刷新UI界面。
  1. // 火柴人位置变量
  2. @State manPostion: {
  3.   x: number,
  4.   y: number
  5. } = { x: 0, y: 0 }
  6. // 背景图位置变量
  7. @State treePosition: {
  8.   x: number,
  9.   y: number
  10. } = { x: 0, y: 0 }
复制代码

给火柴人和背景图片添加位置属性。
  1. Row() {
  2.   Row() {
  3.     Image($r("app.media.man"))
  4.       .height(60)
  5.       .width(545.16)
  6.       // 通过translate实现火柴人的位移。绑定manPosition,用来改变火柴人位置。
  7.       .translate(this.manPostion)
  8.   }
  9.   ...
  10. }
  11. .backgroundImage($r("app.media.background"))
  12. .backgroundImageSize(ImageSize.Cover)
  13. // 通过backgroundImagePosition实现背景图片的位移。绑定treePosition,用来改变背景图片的位置。
  14. .backgroundImagePosition(this.treePosition)
  15. ...
复制代码


3. 为''run''按钮和"stop"按钮绑定控制逻辑。
构建火柴人和背景图片移动的方法,用来设定火柴人和背景图片每次移动的距离。这里要注意火柴人每次移动的距离等于两个火柴人之间的间隔距离(像素值)。
  1. // 火柴人移动方法
  2. manWalk() {
  3.   if (this.manPostion.x <= -517.902) {
  4.     this.manPostion.x = 0
  5.   } else {
  6.     // 每次移动的距离为火柴人静态图像之间的间隔距离
  7.     this.manPostion.x -= 129.69
  8.   }
  9. }
  10. // 背景移动方法
  11. treesMove() {
  12.   if (this.treePosition.x <= -1215) {
  13.     this.treePosition.x = 0
  14.   } else {
  15.     this.treePosition.x -= 20
  16.   }
  17. }
复制代码
创建doAnimation()方法调用上述两个方法,以便在后续的定时器中使用。
  1. doAnimation() {
  2.   this.manWalk()
  3.   this.treesMove()
  4.   }
复制代码
通过setInterval为“run”按钮绑定走动逻辑。
  1. Button('run')
  2.   .margin({ right: 10 })
  3.   .type(ButtonType.Normal)
  4.   .width(75)
  5.   .borderRadius(5)
  6.   .onClick(() => {
  7.     this.clearAllInterval()
  8.     // 创建定时器,调用doAnimation方法,启动动画
  9.     let timer = setInterval(this.doAnimation.bind(this), 100)
  10.     this.timerList.push(timer)
  11.   })
复制代码
通过clearAllInterval为“stop”按钮绑定停止逻辑。
  1. Button('stop')
  2.   .type(ButtonType.Normal)
  3.   .borderRadius(5)
  4.   .width(75)
  5.   .backgroundColor('#ff0000')
  6.   .onClick(() => {
  7.     // 清理定时器,停止动画
  8.     this.clearAllInterval()
  9. })
复制代码

完整代码
本例完整代码如下:
  1. @Entry
  2. @Component
  3. export default struct frameAnimation {
  4.   // 火柴人位置变量
  5.   @State manPostion: {
  6.     x: number,
  7.     y: number
  8.   } = { x: 0, y: 0 }
  9.   // 背景图位置变量
  10.   @State treePosition: {
  11.     x: number,
  12.     y: number
  13.   } = { x: 0, y: 0 }
  14.   // 定时器列表,当列表清空时,动画停止
  15.   private timerList: number[] = []

  16.   // 火柴人移动方法
  17.   manWalk() {
  18.     if (this.manPostion.x <= -517.902) {
  19.       this.manPostion.x = 0
  20.     } else {
  21.       this.manPostion.x -= 129.69
  22.     }
  23.   }
  24.   // 背景移动方法
  25.   treesMove() {
  26.     if (this.treePosition.x <= -1215) {
  27.       this.treePosition.x = 0
  28.     } else {
  29.       this.treePosition.x -= 20
  30.     }
  31.   }

  32.   // 销毁所有定时器
  33.   clearAllInterval() {
  34.     this.timerList.forEach((timer: number) => {
  35.       clearInterval(timer)
  36.     })
  37.     this.timerList = []
  38.   }

  39.   doAnimation() {
  40.     this.manWalk()
  41.     this.treesMove()
  42.   }

  43.   build() {
  44.     Column() {
  45.       // 父Row组件
  46.       Row() {
  47.         // 子Row组件
  48.         Row() {
  49.           // 通过Image组件显示火柴人图像
  50.           Image($r("app.media.man"))
  51.             .height(60)
  52.             .width(545.16)
  53.             // 通过translate实现火柴人的位移。绑定manPosition变量,用来改变火柴人位置。
  54.             .translate(this.manPostion)
  55.         }
  56.         .width(100)
  57.         .justifyContent(FlexAlign.Start)
  58.         .alignItems(VerticalAlign.Top)
  59.         // 截取显示与背景同等大小的区域,控制单个火柴人显示在画面中
  60.         .clip(true)
  61.       }
  62.       // 添加背景图像
  63.       .backgroundImage($r("app.media.background"))
  64.       // 保持宽高比进行缩小或者放大,使得图片两边都大于或等于显示边界。
  65.       .backgroundImageSize(ImageSize.Cover)
  66.       // 通过backgroundImagePosition实现背景图片的位移。绑定treePosition,用来改变背景图片的位置。
  67.       .backgroundImagePosition(this.treePosition)
  68.       .width('100%')
  69.       .height(130)
  70.       .justifyContent(FlexAlign.Center)
  71.       .alignItems(VerticalAlign.Bottom)

  72.       Row() {
  73.         // 添加跑动按钮
  74.         Button('run')
  75.           .margin({ right: 10 })
  76.           .type(ButtonType.Normal)
  77.           .width(75)
  78.           .borderRadius(5)
  79.           .onClick(() => {
  80.             this.clearAllInterval()
  81.             let timer = setInterval(this.doAnimation.bind(this), 100)
  82.             this.timerList.push(timer)
  83.           })
  84.         // 添加停止按钮
  85.         Button('stop')
  86.           .type(ButtonType.Normal)
  87.           .borderRadius(5)
  88.           .width(75)
  89.           .backgroundColor('#ff0000')
  90.           .onClick(() => {
  91.             this.clearAllInterval()
  92.           })
  93.       }.margin({ top: 30, bottom: 10 })
  94.     }.width('100%').width('100%').padding({ top: 30 })
  95.   }
  96. }
复制代码














Copyright   ©2023  OpenHarmony开发者论坛  京ICP备2020036654号-3 |技术支持 Discuz!

返回顶部