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

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

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

最终效果预览

我们将实现以下功能:

  • 点击屏幕任意位置,在点击处生成一个水波纹
  • 触摸并滑动屏幕,波纹会实时跟随手指位置生成
  • 波纹从小到大扩散,同时逐渐消失
  • 波纹颜色随机变化,增加视觉多样性

一、创建基础布局

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

ts 复制代码
@Entry
@Component
struct 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组件很重要,因为我们后续要在这里动态添加水波纹元素。

二、定义水波纹数据结构

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

ts 复制代码
// 水波纹项目类型定义
interface RippleItem {
  id: number      // 唯一标识
  x: number       // 中心点X坐标
  y: number       // 中心点Y坐标
  size: number    // 当前大小
  maxSize: number // 最大大小
  opacity: number // 透明度
  color: string   // 颜色
}

@Entry
@Component
struct 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会自动更新。我们还定义了一些用于跟踪触摸状态和位置的变量。

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

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

ts 复制代码
@Entry
@Component
struct 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)。

四、实现水波纹创建逻辑

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

ts 复制代码
// 创建一个新的水波纹
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方法开始动画(下一步实现)

五、实现动画效果

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

ts 复制代码
// 动画处理波纹的扩散和消失
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. 动画结束后从数组中移除该波纹,释放内存

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

六、实现触摸跟踪功能

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

ts 复制代码
.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. 触摸移动(Move)时

    • 更新触摸位置
    • 保持触摸状态,定时器会在新位置创建波纹
  3. 触摸结束(Up)或取消(Cancel)时

    • 停止触摸状态
    • 清除定时器,不再创建新波纹

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

完整代码

下面是最终的完整代码:

ts 复制代码
// 水波纹项目类型定义
interface RippleItem {
  id: number // 唯一标识
  x: number // 中心点X坐标
  y: number // 中心点Y坐标
  size: number // 当前大小
  maxSize: number // 最大大小
  opacity: number // 透明度
  color: string // 颜色
}

@Entry
@Component
struct 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 触摸跟踪功能,让波纹能够跟随手指滑动

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

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

相关推荐
一个处女座的程序猿O(∩_∩)O4 小时前
鸿蒙Next与API 12深度解析:架构、开发实践与代码示例
华为·架构·harmonyos
月未央4 小时前
HarmonyOS Next 状态管理:Monitor 装饰器实践
ios·harmonyos
苏杰豪5 小时前
鸿蒙特效教程04-直播点赞动画效果实现教程
华为·harmonyos
啊是是是5 小时前
HarmonyOS申请用户位置信息授权和再次授权-系统级API获取地理位置的错误码类型问题getCurrentLocation()
harmonyos
ssr——ssss5 小时前
网络华为HCIA+HCIP数据链路层协议-以太网协议
网络·华为
猿六凯6 小时前
历年云南大学计算机复试上机真题
java·华为od·华为
TARDIS_20207 小时前
OpenHarmony-XTS测试
harmonyos
__Benco7 小时前
OpenHarmony子系统开发 - Rust编译构建指导
开发语言·人工智能·后端·rust·harmonyos
一个处女座的程序猿O(∩_∩)O7 小时前
harmonyOS NEXT开发与前端开发深度对比分析
前端·华为·harmonyos