写点什么

鸿蒙特效教程 03- 水波纹动画效果实现教程

作者:苏杰豪
  • 2025-03-26
    北京
  • 本文字数:6278 字

    阅读完需:约 21 分钟

鸿蒙特效教程 03-水波纹动画效果实现教程

本教程适合 HarmonyOS 初学者,通过简单到复杂的步骤,一步步实现漂亮的水波纹动画效果。

最终效果预览

我们将实现以下功能:


  • 点击屏幕任意位置,在点击处生成一个水波纹

  • 触摸并滑动屏幕,波纹会实时跟随手指位置生成

  • 波纹从小到大扩散,同时逐渐消失

  • 波纹颜色随机变化,增加视觉多样性


一、创建基础布局

首先,我们需要创建一个基础页面布局。这个布局包含一个占满屏幕的区域,用于展示水波纹动画。


@Entry@Componentstruct WaterRipple {  build() {    Column() {      // 水波纹动画效果区域      Stack() {        // 提示文本        Column() {          Text('点击屏幕任意位置产生水波纹')            .fontSize(16)            .fontColor('#333')            .margin({ top: 20 })        }        .width('100%')        .height('100%')        .justifyContent(FlexAlign.Center)        .alignItems(HorizontalAlign.Center)      }      .width('100%')      .height('100%')    }    .width('100%')    .height('100%')    .backgroundColor('#EDEDED')    .expandSafeArea()  }}
复制代码


这段代码创建了一个简单的页面,包含以下元素:


  • 最外层是一个占满整个屏幕的Column

  • 内部是一个Stack组件,它允许我们将多个元素堆叠在一起

  • Stack中放置了一个包含提示文本的Column


Stack组件很重要,因为我们后续要在这里动态添加水波纹元素。

二、定义水波纹数据结构

在实现动画效果之前,我们需要先定义水波纹的数据结构。每个水波纹都有自己的位置、大小、颜色等属性:


// 水波纹项目类型定义interface RippleItem {  id: number      // 唯一标识  x: number       // 中心点X坐标  y: number       // 中心点Y坐标  size: number    // 当前大小  maxSize: number // 最大大小  opacity: number // 透明度  color: string   // 颜色}
@Entry@Componentstruct WaterRipple { // 水波纹数组,用于存储所有活动的水波纹 @State ripples: RippleItem[] = [] // 触摸状态 @State isTouching: boolean = false // 波纹生成定时器 private rippleTimer: number = -1 // 当前触摸位置 private touchX: number = 0 private touchY: number = 0 // 波纹生成间隔(毫秒) private readonly rippleInterval: number = 200 // 其余代码保持不变...}
复制代码


使用@State装饰器标记ripples数组和isTouching状态,这样当它们的内容发生变化时,UI 会自动更新。我们还定义了一些用于跟踪触摸状态和位置的变量。

三、实现波纹绘制与点击事件

接下来,我们需要实现波纹的绘制,并处理基本的点击事件:


@Entry@Componentstruct WaterRipple {  // 前面定义的属性不变...
build() { Column() { // 水波纹动画效果 Stack() { // 水波纹展示区域 Column() { Text('点击屏幕任意位置产生水波纹') .fontSize(16) .fontColor('#333') .margin({ top: 20 }) Text('触摸并滑动,波纹会跟随手指') .fontSize(16) .fontColor('#333') .margin({ top: 10 }) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Center)
// 绘制所有波纹 ForEach(this.ripples, (item: RippleItem) => { Circle() .fill(item.color) .width(item.size) .height(item.size) .opacity(item.opacity) .position({ x: item.x - item.size / 2, y: item.y - item.size / 2 }) }) } .width('100%') .height('100%') // 触摸事件处理将在后面添加 } // 其余代码保持不变... }
// 创建水波纹的方法(暂未实现) createRipple(x: number, y: number) { console.info(`点击位置: x=${x}, y=${y}`) }}
复制代码


在这一步中,我们添加了以下内容:


  1. 使用ForEach遍历ripples数组,为每个波纹创建一个Circle组件

  2. 为提示文本添加了第二行,说明触摸滑动功能

  3. 添加了createRipple方法的基本结构(目前只打印坐标)


注意Circle组件的定位方式:由于圆形是以左上角为基准定位的,而我们希望以圆心定位,所以需要从坐标中减去圆形半径(即 size/2)。

四、实现水波纹创建逻辑

下一步,我们来实现水波纹的创建逻辑:


// 创建一个新的水波纹createRipple(x: number, y: number) {  // 创建随机颜色  const colors = ['#2196F3', '#03A9F4', '#00BCD4', '#4CAF50', '#8BC34A']  const color = colors[Math.floor(Math.random() * colors.length)]
// 创建新波纹 const newRipple: RippleItem = { id: Date.now(), // 使用时间戳作为唯一标识 x: x, // X坐标 y: y, // Y坐标 size: 0, // 初始大小为0 maxSize: 300, // 最大扩散到300像素 opacity: 0.6, // 初始透明度 color: color // 随机颜色 }
// 添加到波纹数组 this.ripples.push(newRipple)
// 启动波纹动画 this.animateRipple(newRipple)}
复制代码


在这个方法中:


  1. 我们定义了一个颜色数组,每次随机选择一种颜色

  2. 创建一个新的波纹对象,初始大小为 0,最大扩散尺寸为 300 像素

  3. 将新波纹添加到数组中,这会触发 UI 更新

  4. 调用animateRipple方法开始动画(下一步实现)

五、实现动画效果

现在,我们来实现波纹的扩散和消失动画:


// 动画处理波纹的扩散和消失animateRipple(ripple: RippleItem) {  let animationStep = 0  const totalSteps = 60        // 总动画帧数  const intervalTime = 16       // 每帧间隔时间(约60fps)  const sizeStep = ripple.maxSize / totalSteps  // 每帧增加的尺寸  const opacityStep = ripple.opacity / totalSteps  // 每帧减少的透明度
const timer = setInterval(() => { animationStep++
// 更新波纹状态 const index = this.ripples.findIndex(item => item.id === ripple.id) if (index !== -1) { // 增加大小 this.ripples[index].size += sizeStep // 降低透明度 this.ripples[index].opacity -= opacityStep
// 更新状态触发重绘 this.ripples = [...this.ripples]
// 动画结束,移除波纹 if (animationStep >= totalSteps) { clearInterval(timer) this.ripples = this.ripples.filter(item => item.id !== ripple.id) } } else { // 波纹已被其他方式移除 clearInterval(timer) } }, intervalTime)}
复制代码


这个方法使用了setInterval定时器来创建动画,主要逻辑包括:


  1. 设置动画参数:总步数、帧间隔、每帧尺寸增量和透明度减量

  2. 每帧更新波纹的大小和透明度,实现从小到大、从清晰到透明的效果

  3. 使用[...this.ripples]创建数组的新实例,触发 UI 更新

  4. 当动画完成(步数达到总步数)或波纹被移除时,清除定时器

  5. 动画结束后从数组中移除该波纹,释放内存


至此,我们已经实现了基本的水波纹效果。下一步将添加触摸滑动功能。

六、实现触摸跟踪功能

最后,我们来实现触摸跟踪功能,让波纹能够跟随手指移动:


.onTouch((event: TouchEvent) => {  // 获取当前触摸点坐标  this.touchX = event.touches[0].x  this.touchY = event.touches[0].y    // 根据触摸状态处理  switch (event.type) {    case TouchType.Down:      // 开始触摸,立即创建一个波纹      this.isTouching = true      this.createRipple(this.touchX, this.touchY)            // 启动定时器连续生成波纹      if (this.rippleTimer === -1) {        this.rippleTimer = setInterval(() => {          if (this.isTouching) {            // 添加小偏移让效果更自然            const offsetX = Math.random() * 10 - 5            const offsetY = Math.random() * 10 - 5            this.createRipple(this.touchX + offsetX, this.touchY + offsetY)          }        }, this.rippleInterval)      }      break          case TouchType.Move:      // 移动时保持触摸状态,波纹会在定时器中根据新坐标创建      this.isTouching = true      break          case TouchType.Up:    case TouchType.Cancel:      // 触摸结束,停止生成波纹      this.isTouching = false      if (this.rippleTimer !== -1) {        clearInterval(this.rippleTimer)        this.rippleTimer = -1      }      break  }})
复制代码


在这段代码中,我们使用onTouch事件监听器来处理触摸事件,主要功能包括:


  1. 触摸开始(Down)时

  2. 记录触摸位置

  3. 立即创建一个波纹

  4. 启动定时器,以固定间隔创建波纹

  5. 触摸移动(Move)时

  6. 更新触摸位置

  7. 保持触摸状态,定时器会在新位置创建波纹

  8. 触摸结束(Up)或取消(Cancel)时

  9. 停止触摸状态

  10. 清除定时器,不再创建新波纹


这样实现后,当用户触摸并滑动屏幕时,波纹会实时跟随手指位置生成,创造出一种水流般的视觉效果。

完整代码

下面是最终的完整代码:


// 水波纹项目类型定义interface RippleItem {  id: number // 唯一标识  x: number // 中心点X坐标  y: number // 中心点Y坐标  size: number // 当前大小  maxSize: number // 最大大小  opacity: number // 透明度  color: string // 颜色}
@Entry@Componentstruct WaterRipple { // 水波纹数组 @State ripples: RippleItem[] = [] // 触摸状态 @State isTouching: boolean = false // 波纹生成定时器 private rippleTimer: number = -1 // 当前触摸位置 private touchX: number = 0 private touchY: number = 0 // 波纹生成间隔(毫秒) private readonly rippleInterval: number = 200
build() { Column() { // 水波纹动画效果 Stack() { // 水波纹展示区域 Column() { Text('点击屏幕任意位置产生水波纹') .fontSize(16) .fontColor('#333') .margin({ top: 20 }) Text('触摸并滑动,波纹会跟随手指') .fontSize(16) .fontColor('#333') .margin({ top: 10 }) } .width('100%') .height('100%') .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Center)
// 绘制所有波纹 ForEach(this.ripples, (item: RippleItem) => { Circle() .fill(item.color) .width(item.size) .height(item.size) .opacity(item.opacity) .position({ x: item.x - item.size / 2, y: item.y - item.size / 2 }) }) } .width('100%') .height('100%') .onTouch((event: TouchEvent) => { // 获取当前触摸点坐标 this.touchX = event.touches[0].x this.touchY = event.touches[0].y // 根据触摸状态处理 switch (event.type) { case TouchType.Down: // 开始触摸,立即创建一个波纹 this.isTouching = true this.createRipple(this.touchX, this.touchY) // 启动定时器连续生成波纹 if (this.rippleTimer === -1) { this.rippleTimer = setInterval(() => { if (this.isTouching) { // 添加小偏移让效果更自然 const offsetX = Math.random() * 10 - 5 const offsetY = Math.random() * 10 - 5 this.createRipple(this.touchX + offsetX, this.touchY + offsetY) } }, this.rippleInterval) } break case TouchType.Move: // 移动时保持触摸状态,波纹会在定时器中根据新坐标创建 this.isTouching = true break case TouchType.Up: case TouchType.Cancel: // 触摸结束,停止生成波纹 this.isTouching = false if (this.rippleTimer !== -1) { clearInterval(this.rippleTimer) this.rippleTimer = -1 } break } }) } .width('100%') .height('100%') .backgroundColor('#EDEDED') .expandSafeArea() }
// 创建一个新的水波纹 createRipple(x: number, y: number) { // 创建随机颜色 const colors = ['#2196F3', '#03A9F4', '#00BCD4', '#4CAF50', '#8BC34A'] const color = colors[Math.floor(Math.random() * colors.length)]
// 创建新波纹 const newRipple: RippleItem = { id: Date.now(), x: x, y: y, size: 0, maxSize: 300, opacity: 0.6, color: color }
// 添加到波纹数组 this.ripples.push(newRipple)
// 启动波纹动画 this.animateRipple(newRipple) }
// 动画处理波纹的扩散和消失 animateRipple(ripple: RippleItem) { let animationStep = 0 const totalSteps = 60 const intervalTime = 16 // 约60fps const sizeStep = ripple.maxSize / totalSteps const opacityStep = ripple.opacity / totalSteps
const timer = setInterval(() => { animationStep++
// 更新波纹状态 const index = this.ripples.findIndex(item => item.id === ripple.id) if (index !== -1) { // 增加大小 this.ripples[index].size += sizeStep // 降低透明度 this.ripples[index].opacity -= opacityStep
// 更新状态触发重绘 this.ripples = [...this.ripples]
// 动画结束,移除波纹 if (animationStep >= totalSteps) { clearInterval(timer) this.ripples = this.ripples.filter(item => item.id !== ripple.id) } } else { // 波纹已被其他方式移除 clearInterval(timer) } }, intervalTime) }}
复制代码

总结

通过这个教程,我们学习了如何一步步实现水波纹动画效果:


  1. ArkUI 搭建基础布局,创建用于展示水波纹的容器

  2. @State 定义水波纹数据结构,设计存储和管理波纹的方式

  3. 实现基本的波纹绘制和触摸事件 onTouch

  4. 创建水波纹生成逻辑,包括随机颜色 Math.random

  5. 使用 setInterval 定时器实现波纹扩散和消失的动画效果

  6. 添加 TouchEvent 触摸跟踪功能,让波纹能够跟随手指滑动


这个简单而美观的水波纹效果可以应用在你的应用中的各种交互场景,例如按钮点击、图片查看、页面切换等。通过调整参数,你还可以创造出不同风格的波纹效果。


希望这个教程对你有所帮助,祝你在鸿蒙应用开发中创造出更多精彩的交互体验!

发布于: 1 小时前阅读数: 11
用户头像

苏杰豪

关注

鸿蒙很开门~ 2019-03-30 加入

传智教育、黑马程序员课程研究员

评论

发布
暂无评论
鸿蒙特效教程03-水波纹动画效果实现教程_鸿蒙_苏杰豪_InfoQ写作社区