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

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

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

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

开发环境准备

  • DevEco Studio 5.0.3
  • HarmonyOS Next API 15

下载代码仓库

最终效果预览

我们将实现以下功能:

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

特效03-水波纹-效果.gif

一、创建基础布局

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

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

二、定义水波纹数据结构

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

// 水波纹项目类型定义
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会自动更新。我们还定义了一些用于跟踪触摸状态和位置的变量。

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

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

@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)。

四、实现水波纹创建逻辑

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

// 创建一个新的水波纹
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. 触摸移动(Move)时

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

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

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

完整代码

下面是最终的完整代码:

// 水波纹项目类型定义
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 触摸跟踪功能,让波纹能够跟随手指滑动

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

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

目录
相关文章
|
5天前
鸿蒙开发:平移动画时间为啥没了?
问题的原因,第一个,由于键值发生了变化,造成了组件重新创建,第二个,由于组件重新创建,动画时机过早,造成属性未生效。
鸿蒙开发:平移动画时间为啥没了?
|
3天前
|
安全 Go 开发工具
HarmonyOS5云服务技术分享--手机号登录教程
本文详细讲解了在HarmonyOS中集成手机号认证功能的全流程。首先分析了手机号认证的优势,如用户友好、安全性强及快速接入。接着介绍了环境准备步骤,包括集成AGC认证SDK、开启手机认证能力及添加必要权限。核心功能实现部分提供了新用户注册、密码登录和验证码登录的代码示例。此外,还涵盖了账号管理技巧,如修改绑定手机号、重置密码等,并提供了避坑指南和扩展能力建议,帮助开发者轻松实现安全高效的认证系统。
|
2月前
|
API 人机交互 Android开发
鸿蒙特效教程09-深入学习animateTo动画
本教程将带领大家从零开始,一步步讲解如何讲解 animateTo 动画,并实现按钮交互效果,使新手也能轻松掌握。
90 6
鸿蒙特效教程09-深入学习animateTo动画
|
2月前
|
安全 UED 索引
鸿蒙特效教程06-可拖拽网格实现教程
本教程适合 HarmonyOS Next 初学者,通过简单到复杂的步骤,一步步实现类似桌面APP中的可拖拽编辑效果。
103 1
鸿蒙特效教程06-可拖拽网格实现教程
|
2月前
|
前端开发 算法 UED
鸿蒙特效教程04-直播点赞动画效果实现教程
本教程适合HarmonyOS初学者,通过简单到复杂的步骤,通过HarmonyOS的Canvas组件,一步步实现时下流行的点赞动画效果。
105 1
鸿蒙特效教程04-直播点赞动画效果实现教程
|
2月前
|
API UED 开发者
HarmonyOS:动画 motionPath 、 animateToImmediately API自学指南
在鸿蒙应用开发中,动画是提升用户体验的关键。本文针对初学者面对众多动画API时的困惑,重点解析两个实用API:`motionPath`和`animateToImmediately`。前者通过精细控制组件运动路径(如SVG字符串定义轨迹),实现灵动位移动画;后者从API Version 12起支持显式动画立即下发,结合状态变化打造流畅动画序列。文中提供详细参数说明与示例代码,帮助开发者快速掌握技巧,让应用更生动。
82 8
|
2月前
|
前端开发 JavaScript API
HarmonyOS:ArkTS 显式动画 animateTo 自学指南
本文深入解析了 ArkTS 中的 `animateTo` 全局显式动画接口,帮助开发者掌握其使用方法。文章从接口概述、参数详解到使用注意事项,结合实际示例代码,全面展示了如何通过配置 `AnimateParam` 对象实现流畅的动画效果。内容涵盖属性动画、布局变化及组件转场等场景,并强调不同版本的支持特性。适合初学者系统学习,也供进阶开发者参考优化动画体验。希望本文能助你快速上手 `animateTo`!
142 7
|
2月前
|
前端开发 算法 API
鸿蒙特效教程08-幸运大转盘抽奖
本教程将带领大家从零开始,一步步实现一个完整的转盘抽奖效果,包括界面布局、Canvas绘制、动画效果和抽奖逻辑等。
109 0
鸿蒙特效教程08-幸运大转盘抽奖
|
2月前
|
存储 API UED
鸿蒙特效教程02-微信语音录制动画效果实现教程
本教程适合HarmonyOS初学者,通过简单到复杂的步骤,一步步实现类似微信APP中的语音录制动画效果。
139 0
鸿蒙特效教程02-微信语音录制动画效果实现教程
|
3天前
|
Android开发 容器
鸿蒙开发:使用nestedScroll解决滑动冲突
nestedScroll属性的作用,主要是,用于设置嵌套滚动选项,设置前后两个方向的嵌套滚动模式,实现与父组件的滚动联动。
39 19
鸿蒙开发:使用nestedScroll解决滑动冲突