写点什么

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

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

    阅读完需:约 25 分钟

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

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

最终效果预览

我们将实现以下功能:


  1. 长按"按住说话"按钮:显示录音界面和声波动画

  2. 录音过程中显示实时时长

  3. 手指上滑:取消录音发送

  4. 松开手指:根据状态发送或取消录音


一、基础布局实现

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


@Entry@Componentstruct 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@Componentstruct 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@Componentstruct 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@Componentstruct 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@Componentstruct 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@Componentstruct 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 小时前阅读数: 14
用户头像

苏杰豪

关注

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

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

评论

发布
暂无评论
鸿蒙特效教程02-微信语音录制动画效果实现教程_鸿蒙_苏杰豪_InfoQ写作社区