鸿蒙特效教程02-微信语音录制动画效果实现教程

简介: 本教程适合HarmonyOS初学者,通过简单到复杂的步骤,一步步实现类似微信APP中的语音录制动画效果。

鸿蒙特效教程02-微信语音录制动画效果实现教程

本教程适合HarmonyOS初学者,通过简单到复杂的步骤,一步步实现类似微信APP中的语音录制动画效果。

开发环境准备

  • DevEco Studio 5.0.3
  • HarmonyOS Next API 15

下载代码仓库

最终效果预览

我们将实现以下功能:

  1. 长按"按住说话"按钮:显示录音界面和声波动画
  2. 录音过程中显示实时时长
  3. 手指上滑:取消录音发送
  4. 松开手指:根据状态发送或取消录音

特效02-微信语音-效果.gif

一、基础布局实现

首先,我们需要创建基本的界面布局,模拟微信聊天界面的结构。

@Entry
@Component
struct WeChatRecorder {
   
  build() {
   
    Column() {
   
      // 聊天内容区域(模拟)
      Stack({
    alignContent: Alignment.Center }) {
   
      }
      .layoutWeight(1)

      // 底部输入栏
      Row() {
   
        // 录音按钮
        Text('按住 说话')
          .fontSize(16)
          .fontColor('#333333')
          .backgroundColor('#F5F5F5')
          .borderRadius(4)
          .textAlign(TextAlign.Center)
          .width('100%')
          .height(40)
          .padding({
    left: 10, right: 10 })
      }
      .width('100%')
      .backgroundColor(Color.White)
      .expandSafeArea()
      .padding({
    left: 15, right: 15, top: 15 })
      .border({
    width: {
    top: 1 }, color: '#E5E5E5' })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#EDEDED')
    .expandSafeArea()
  }
}

这一步我们创建了一个基本的聊天界面布局,包含两部分:

  1. 顶部聊天内容区域:使用Stack布局,目前为空
  2. 底部输入栏:包含一个"按住 说话"按钮

二、添加状态变量

接下来,我们需要添加一些状态变量来跟踪录音状态和动画效果。

@Entry
@Component
struct WeChatRecorder {
   
  // 是否正在录音
  @State isRecording: boolean = false
  // 是否显示取消提示(上滑状态)
  @State isCancel: boolean = false
  // 录音时长(秒)
  @State recordTime: number = 0
  // 声波高度变化数组
  @State waveHeights: number[] = [20, 30, 25, 40, 35, 28, 32, 37]
  // 计时器ID
  private timerId: number = 0
  // 波形动画计时器ID
  private waveTimerId: number = 0
  // 触摸起始位置
  private touchStartY: number = 0
  // 触摸移动阈值,超过该值显示取消提示
  private readonly cancelThreshold: number = 50

  build() {
   
    // 之前的布局代码
  }
}

我们添加了以下状态变量:

  1. isRecording:跟踪是否正在录音
  2. isCancel:跟踪是否处于取消录音状态(上滑)
  3. recordTime:记录录音时长(秒)
  4. waveHeights:存储声波高度数组,用于实现波形动画
  5. timerId:存储计时器ID,用于后续清除
  6. waveTimerId:存储波形动画计时器ID
  7. touchStartY:记录触摸起始位置,用于计算上滑距离
  8. cancelThreshold:定义上滑多少距离触发取消状态

三、添加基础方法

在实现UI交互前,我们先添加一些基础方法来处理录音状态和动画效果。

@Entry
@Component
struct WeChatRecorder {
   
  // 状态变量定义...

  /**
   * 开始录音,初始化状态及启动计时器
   */
  startRecording() {
   
    this.isRecording = true
    this.isCancel = false
    this.recordTime = 0

    // 启动计时器,每秒更新录音时长
    this.timerId = setInterval(() => {
   
      this.recordTime++
    }, 1000)

    // 启动波形动画计时器,随机更新波形高度
    this.waveTimerId = setInterval(() => {
   
      this.updateWaveHeights()
    }, 200)
  }

  /**
   * 结束录音,清理计时器和状态
   */
  stopRecording() {
   
    // 清除计时器
    if (this.timerId !== 0) {
   
      clearInterval(this.timerId)
      this.timerId = 0
    }

    if (this.waveTimerId !== 0) {
   
      clearInterval(this.waveTimerId)
      this.waveTimerId = 0
    }

    // 如果是取消状态,则显示取消提示
    if (this.isCancel) {
   
      console.info('录音已取消')
    } else if (this.recordTime > 0) {
   
      // 如果录音时长大于0,则模拟发送语音
      console.info(`发送语音,时长: ${
     this.recordTime}秒`)
    }

    // 重置状态
    this.isRecording = false
    this.isCancel = false
    this.recordTime = 0
  }

  /**
   * 更新波形高度以产生动画效果
   */
  updateWaveHeights() {
   
    // 创建新的波形高度数组
    const newHeights = this.waveHeights.map(() => {
   
      // 生成20-40之间的随机高度
      return Math.floor(Math.random() * 20) + 20
    })

    this.waveHeights = newHeights
  }

  /**
   * 格式化时间显示,将秒转换为"00:00"格式
   */
  formatTime(seconds: number): string {
   
    const minutes = Math.floor(seconds / 60)
    const secs = seconds % 60
    return `${
     minutes.toString()
      .padStart(2, '0')}:${
     secs.toString()
      .padStart(2, '0')}`
  }

  aboutToDisappear() {
   
    // 组件销毁时清除计时器
    if (this.timerId !== 0) {
   
      clearInterval(this.timerId)
      this.timerId = 0
    }

    if (this.waveTimerId !== 0) {
   
      clearInterval(this.waveTimerId)
      this.waveTimerId = 0
    }
  }

  build() {
   
    // 之前的布局代码
  }
}

在这一步中,我们实现了以下方法:

  1. startRecording:开始录音,初始化状态并启动计时器
  2. stopRecording:结束录音,清理计时器和状态
  3. updateWaveHeights:更新波形高度数组,产生动画效果
  4. formatTime:将秒数格式化为"00:00"格式的时间显示
  5. aboutToDisappear:组件销毁时清理计时器,防止内存泄漏

四、实现长按事件处理

接下来,我们为"按住 说话"按钮添加触摸事件处理,实现长按开始录音的功能。

@Entry
@Component
struct WeChatRecorder {
   
  // 之前的代码...

  build() {
   
    Column() {
   
      Stack({
    alignContent: Alignment.Center }) {
   
        // 暂时留空,后面会添加录音界面
      }
      .layoutWeight(1)

      // 底部输入栏
      Row() {
   
        // 录音按钮
        Text(this.isRecording ? '松开 发送' : '按住 说话')
          .fontSize(16)
          .fontColor(this.isRecording ? Color.White : '#333333')
          .backgroundColor(this.isRecording ? '#07C160' : '#F5F5F5')
          .borderRadius(4)
          .textAlign(TextAlign.Center)
          .width('100%')
          .height(40)
          .padding({
    left: 10, right: 10 })
          // 添加触摸事件
          .onTouch((event) => {
   
            if (event.type === TouchType.Down) {
   
              // 按下时,记录起始位置,开始录音
              this.touchStartY = event.touches[0].y
              this.startRecording()
            } else if (event.type === TouchType.Move) {
   
              // 移动时,检测是否上滑到取消区域
              const moveDistance = this.touchStartY - event.touches[0].y
              this.isCancel = moveDistance > this.cancelThreshold
            } else if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
   
              // 松开或取消触摸时,结束录音
              this.stopRecording()
            }
          })
      }
      .width('100%')
      .backgroundColor(this.isRecording ? Color.Transparent : Color.White)
      .expandSafeArea()
      .padding({
    left: 15, right: 15, top: 15 })
      .border({
    width: {
    top: 1 }, color: '#E5E5E5' })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#EDEDED')
    .expandSafeArea()
  }
}

在这一步中,我们:

  1. 为按钮文本添加了动态内容,根据录音状态显示不同文字
  2. 为按钮添加了触摸事件处理,包括按下、移动和松开/取消
  3. 根据录音状态动态改变底部栏的背景色

五、实现录音界面和声波动画

最后,我们添加录音状态下的界面显示,包括上滑取消提示和声波动画。

@Entry
@Component
struct WeChatRecorder {
   
  // 之前的代码...

  build() {
   
    Column() {
   
      // 聊天内容区域
      Stack({
    alignContent: Alignment.Center }) {
   
        // 录音状态提示
        if (this.isRecording) {
   
          // 遮罩背景
          Column()
            .width('100%')
            .height('100%')
            .backgroundColor('#80000000')
            .expandSafeArea()

          Column() {
   
            // 上滑取消提示
            Text(this.isCancel ? '松开手指,取消发送' : '手指上滑,取消发送')
              .fontSize(14)
              .fontColor(this.isCancel ? Color.Red : '#999999')
              .backgroundColor(this.isCancel ? '#FFE9E9' : '#D1D1D1')
              .borderRadius(4)
              .padding({
   
                left: 10,
                right: 10,
                top: 5,
                bottom: 5
              })
              .margin({
    bottom: 20 })

            // 录音界面容器
            Column() {
   
              // 声波动画容器
              Row() {
   
                ForEach(this.waveHeights, (height: number, index) => {
   
                  Column()
                    .width(4)
                    .height(height)
                    .backgroundColor('#7ED321')
                    .borderRadius(2)
                    .margin({
    left: 3, right: 3 })
                })
              }
              .width(160)
              .height(100)
              .justifyContent(FlexAlign.Center)
              .margin({
    bottom: 15 })

              // 录音时间显示
              Text(`${
     this.formatTime(this.recordTime)}`)
                .fontSize(16)
                .fontColor('#999999')
            }
            .width(180)
            .backgroundColor(Color.White)
            .borderRadius(8)
            .justifyContent(FlexAlign.Center)
            .padding(10)
          }
          .width('100%')
          .height('100%')
          .justifyContent(FlexAlign.Center)
        }
      }
      .layoutWeight(1)

      // 底部输入栏
      // 与之前的代码相同
    }
    // 与之前的代码相同
  }
}

在这一步中,我们添加了:

  1. 录音状态下的遮罩背景,使用半透明黑色背景
  2. 上滑取消提示,根据 isCancel 状态显示不同内容和样式
  3. 声波动画容器,使用 ForEach 循环遍历 waveHeights 数组创建多个柱状条
  4. 录音时间显示,使用 formatTime 方法格式化时间

六、完整实现

下面是完整的实现代码:

/**
 * 微信语音录制动画效果
 * 实现功能:
 * 1. 长按按钮: 显示录音动画
 * 2. 上滑取消: 模拟取消录音
 * 3. 松开发送: 模拟发送语音
 */
@Entry
@Component
struct WeChatRecorder {
   
  // 是否正在录音
  @State isRecording: boolean = false
  // 是否显示取消提示(上滑状态)
  @State isCancel: boolean = false
  // 录音时长(秒)
  @State recordTime: number = 0
  // 声波高度变化数组
  @State waveHeights: number[] = [20, 30, 25, 40, 35, 28, 32, 37]
  // 计时器ID
  private timerId: number = 0
  // 波形动画计时器ID
  private waveTimerId: number = 0
  // 触摸起始位置
  private touchStartY: number = 0
  // 触摸移动阈值,超过该值显示取消提示
  private readonly cancelThreshold: number = 50

  /**
   * 开始录音,初始化状态及启动计时器
   */
  startRecording() {
   
    this.isRecording = true
    this.isCancel = false
    this.recordTime = 0

    // 启动计时器,每秒更新录音时长
    this.timerId = setInterval(() => {
   
      this.recordTime++
    }, 1000)

    // 启动波形动画计时器,随机更新波形高度
    this.waveTimerId = setInterval(() => {
   
      this.updateWaveHeights()
    }, 200)
  }

  /**
   * 结束录音,清理计时器和状态
   */
  stopRecording() {
   
    // 清除计时器
    if (this.timerId !== 0) {
   
      clearInterval(this.timerId)
      this.timerId = 0
    }

    if (this.waveTimerId !== 0) {
   
      clearInterval(this.waveTimerId)
      this.waveTimerId = 0
    }

    // 如果是取消状态,则显示取消提示
    if (this.isCancel) {
   
      console.info('录音已取消')
    } else if (this.recordTime > 0) {
   
      // 如果录音时长大于0,则模拟发送语音
      console.info(`发送语音,时长: ${
     this.recordTime}秒`)
    }

    // 重置状态
    this.isRecording = false
    this.isCancel = false
    this.recordTime = 0
  }

  /**
   * 更新波形高度以产生动画效果
   */
  updateWaveHeights() {
   
    // 创建新的波形高度数组
    const newHeights = this.waveHeights.map(() => {
   
      // 生成20-40之间的随机高度
      return Math.floor(Math.random() * 20) + 20
    })

    this.waveHeights = newHeights
  }

  /**
   * 格式化时间显示,将秒转换为"00:00"格式
   */
  formatTime(seconds: number): string {
   
    const minutes = Math.floor(seconds / 60)
    const secs = seconds % 60
    return `${
     minutes.toString()
      .padStart(2, '0')}:${
     secs.toString()
      .padStart(2, '0')}`
  }

  aboutToDisappear() {
   
    // 组件销毁时清除计时器
    if (this.timerId !== 0) {
   
      clearInterval(this.timerId)
      this.timerId = 0
    }

    if (this.waveTimerId !== 0) {
   
      clearInterval(this.waveTimerId)
      this.waveTimerId = 0
    }
  }

  build() {
   
    Column() {
   
      // 聊天内容区域(模拟)
      Stack({
    alignContent: Alignment.Center }) {
   
        // 录音状态提示
        if (this.isRecording) {
   
          // 遮罩背景
          Column()
            .width('100%')
            .height('100%')
            .backgroundColor('#80000000')
            .expandSafeArea()

          Column() {
   
            // 上滑取消提示
            Text(this.isCancel ? '松开手指,取消发送' : '手指上滑,取消发送')
              .fontSize(14)
              .fontColor(this.isCancel ? Color.Red : '#999999')
              .backgroundColor(this.isCancel ? '#FFE9E9' : '#D1D1D1')
              .borderRadius(4)
              .padding({
   
                left: 10,
                right: 10,
                top: 5,
                bottom: 5
              })
              .margin({
    bottom: 20 })

            // 录音界面容器
            Column() {
   
              // 声波动画容器
              Row() {
   
                ForEach(this.waveHeights, (height: number, index) => {
   
                  Column()
                    .width(4)
                    .height(height)
                    .backgroundColor('#7ED321')
                    .borderRadius(2)
                    .margin({
    left: 3, right: 3 })
                })
              }
              .width(160)
              .height(100)
              .justifyContent(FlexAlign.Center)
              .margin({
    bottom: 15 })

              // 录音时间显示
              Text(`${
     this.formatTime(this.recordTime)}`)
                .fontSize(16)
                .fontColor('#999999')
            }
            .width(180)
            .backgroundColor(Color.White)
            .borderRadius(8)
            .justifyContent(FlexAlign.Center)
            .padding(10)
          }
          .width('100%')
          .height('100%')
          .justifyContent(FlexAlign.Center)
        }
      }
      .layoutWeight(1)

      // 底部输入栏
      Row() {
   
        // 录音按钮
        Text(this.isRecording ? '松开 发送' : '按住 说话')
          .fontSize(16)
          .fontColor(this.isRecording ? Color.White : '#333333')
          .backgroundColor(this.isRecording ? '#07C160' : '#F5F5F5')
          .borderRadius(4)
          .textAlign(TextAlign.Center)
          .width('100%')
          .height(40)
          .padding({
    left: 10, right: 10 })// 添加触摸事件
          .onTouch((event) => {
   
            if (event.type === TouchType.Down) {
   
              // 按下时,记录起始位置,开始录音
              this.touchStartY = event.touches[0].y
              this.startRecording()
            } else if (event.type === TouchType.Move) {
   
              // 移动时,检测是否上滑到取消区域
              const moveDistance = this.touchStartY - event.touches[0].y
              this.isCancel = moveDistance > this.cancelThreshold
            } else if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
   
              // 松开或取消触摸时,结束录音
              this.stopRecording()
            }
          })
      }
      .width('100%')
      .backgroundColor(this.isRecording ? Color.Transparent : Color.White)
      .expandSafeArea()
      .padding({
    left: 15, right: 15, top: 15 })
      .border({
    width: {
    top: 1 }, color: '#E5E5E5' })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#EDEDED')
    .expandSafeArea()
  }
}

拓展与优化

以上是基本的实现,如果想进一步优化,可以考虑:

  1. 真实的录音功能:使用HarmonyOS的媒体录制API实现实际录音
  2. 声音波形实时变化:根据实际录音音量调整波形高度
  3. 振动反馈:在录音开始、取消或发送时添加振动反馈
  4. 显示已录制的语音消息:将录制好的语音添加到聊天消息列表中
  5. 录音时长限制:添加最长录音时间限制(如微信的60秒)

总结

通过这个教程,我们从零开始实现了类似微信的语音录制动画效果。主要用到了以下技术:

  1. HarmonyOS的ArkUI布局系统
  2. 状态管理(@State)
  3. 触摸事件处理
  4. 定时器和动画
  5. 条件渲染
  6. 组件生命周期处理

这些技术和概念不仅适用于这个特定效果,还可以应用于各种交互设计中。希望这个教程能帮助你更好地理解HarmonyOS开发,并创建出更加精美的应用界面!

目录
相关文章
|
1月前
|
Serverless 人机交互 UED
鸿蒙特效教程01-哔哩哔哩点赞与一键三连效果实现教程
本教程面向HarmonyOS初学者,详细讲解如何实现类似哔哩哔哩APP中的点赞与一键三连效果。内容涵盖基础布局、状态切换、点击动画、长按手势识别、旋转缩放动画以及粒子爆炸效果的实现。通过ArkUI布局系统、状态管理、手势处理和动画技术,逐步完成从简单到复杂的交互设计。最终效果包括图标变色、缩放、旋转及粒子动画,为用户提供流畅生动的体验。适合希望掌握HarmonyOS开发技巧的开发者学习参考。
176 67
|
30天前
|
API 人机交互 Android开发
鸿蒙特效教程09-深入学习animateTo动画
本教程将带领大家从零开始,一步步讲解如何讲解 animateTo 动画,并实现按钮交互效果,使新手也能轻松掌握。
73 6
|
30天前
|
安全 UED 索引
鸿蒙特效教程06-可拖拽网格实现教程
本教程适合 HarmonyOS Next 初学者,通过简单到复杂的步骤,一步步实现类似桌面APP中的可拖拽编辑效果。
93 1
|
30天前
|
前端开发 算法 UED
鸿蒙特效教程04-直播点赞动画效果实现教程
本教程适合HarmonyOS初学者,通过简单到复杂的步骤,通过HarmonyOS的Canvas组件,一步步实现时下流行的点赞动画效果。
91 1
|
29天前
|
API UED 开发者
HarmonyOS:动画 motionPath 、 animateToImmediately API自学指南
在鸿蒙应用开发中,动画是提升用户体验的关键。本文针对初学者面对众多动画API时的困惑,重点解析两个实用API:`motionPath`和`animateToImmediately`。前者通过精细控制组件运动路径(如SVG字符串定义轨迹),实现灵动位移动画;后者从API Version 12起支持显式动画立即下发,结合状态变化打造流畅动画序列。文中提供详细参数说明与示例代码,帮助开发者快速掌握技巧,让应用更生动。
57 8
|
29天前
|
前端开发 JavaScript API
HarmonyOS:ArkTS 显式动画 animateTo 自学指南
本文深入解析了 ArkTS 中的 `animateTo` 全局显式动画接口,帮助开发者掌握其使用方法。文章从接口概述、参数详解到使用注意事项,结合实际示例代码,全面展示了如何通过配置 `AnimateParam` 对象实现流畅的动画效果。内容涵盖属性动画、布局变化及组件转场等场景,并强调不同版本的支持特性。适合初学者系统学习,也供进阶开发者参考优化动画体验。希望本文能助你快速上手 `animateTo`!
85 7
|
30天前
|
前端开发 算法 API
鸿蒙特效教程08-幸运大转盘抽奖
本教程将带领大家从零开始,一步步实现一个完整的转盘抽奖效果,包括界面布局、Canvas绘制、动画效果和抽奖逻辑等。
97 0
|
30天前
|
存储 容器 API
鸿蒙特效教程03-水波纹动画效果实现教程
本教程适合HarmonyOS初学者,通过简单到复杂的步骤,一步步实现漂亮的水波纹动画效果。
102 0
|
2月前
|
自然语言处理 搜索推荐 小程序
微信公众号接口:解锁公众号开发的无限可能
微信公众号接口是微信官方提供的API,支持开发者通过编程与公众号交互,实现自动回复、消息管理、用户管理和数据分析等功能。本文深入探讨接口的定义、类型、优势及应用场景,如智能客服、内容分发、电商闭环等,并介绍开发流程和工具,帮助运营者提升用户体验和效率。未来,随着微信生态的发展,公众号接口将带来更多机遇,如小程序融合、AI应用等。
|
21天前
|
小程序 Java 关系型数据库
weixin163基于微信小程序的校园二手交易平台系统设计与开发ssm(文档+源码)_kaic
本文介绍了一款基于微信小程序的校园二手物品交易平台的开发与实现。该平台采用Java语言开发服务端,使用MySQL数据库进行数据存储,前端以微信小程序为载体,支持管理员和学生两种角色操作。管理员可管理用户、商品分类及信息、交易记录等,而学生则能注册登录、发布购买商品、参与交流论坛等。系统设计注重交互性和安全性,通过SSM框架优化开发流程,确保高效稳定运行,满足用户便捷交易的需求,推动校园资源共享与循环利用。
下一篇
oss创建bucket