鸿蒙殊效教程02-微信语音录制动画

种地  论坛元老 | 2025-3-28 17:32:01 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 1709|帖子 1709|积分 5127

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

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



  • DevEco Studio 5.0.3
  • HarmonyOS Next API 15
终极效果预览

我们将实现以下功能:

  • 长按"按住语言"按钮:显示录音界面和声颠簸画
  • 录音过程中显示及时时长
  • 手指上滑:取消录音发送
  • 松开手指:根据状态发送或取消录音

一、底子布局实现

首先,我们须要创建根本的界面布局,模拟微信谈天界面的结构。
  1. @Entry
  2. @Component
  3. struct WeChatRecorder {
  4.   build() {
  5.     Column() {
  6.       // 聊天内容区域(模拟)
  7.       Stack({ alignContent: Alignment.Center }) {
  8.       }
  9.       .layoutWeight(1)
  10.       
  11.       // 底部输入栏
  12.       Row() {
  13.         // 录音按钮
  14.         Text('按住 说话')
  15.           .fontSize(16)
  16.           .fontColor('#333333')
  17.           .backgroundColor('#F5F5F5')
  18.           .borderRadius(4)
  19.           .textAlign(TextAlign.Center)
  20.           .width('100%')
  21.           .height(40)
  22.           .padding({ left: 10, right: 10 })
  23.       }
  24.       .width('100%')
  25.       .backgroundColor(Color.White)
  26.       .expandSafeArea()
  27.       .padding({ left: 15, right: 15, top: 15 })
  28.       .border({ width: { top: 1 }, color: '#E5E5E5' })
  29.     }
  30.     .width('100%')
  31.     .height('100%')
  32.     .backgroundColor('#EDEDED')
  33.     .expandSafeArea()
  34.   }
  35. }
复制代码
这一步我们创建了一个根本的谈天界面布局,包含两部分:

  • 顶部谈天内容地区:利用Stack布局,现在为空
  • 底部输入栏:包含一个"按住 语言"按钮
二、添加状态变量

接下来,我们须要添加一些状态变量来跟踪录音状态和动画效果。
  1. @Entry
  2. @Component
  3. struct WeChatRecorder {
  4.   // 是否正在录音
  5.   @State isRecording: boolean = false
  6.   // 是否显示取消提示(上滑状态)
  7.   @State isCancel: boolean = false
  8.   // 录音时长(秒)
  9.   @State recordTime: number = 0
  10.   // 声波高度变化数组
  11.   @State waveHeights: number[] = [20, 30, 25, 40, 35, 28, 32, 37]
  12.   // 计时器ID
  13.   private timerId: number = 0
  14.   // 波形动画计时器ID
  15.   private waveTimerId: number = 0
  16.   // 触摸起始位置
  17.   private touchStartY: number = 0
  18.   // 触摸移动阈值,超过该值显示取消提示
  19.   private readonly cancelThreshold: number = 50
  20.   build() {
  21.     // 之前的布局代码
  22.   }
  23. }
复制代码
我们添加了以下状态变量:

  • isRecording:跟踪是否正在录音
  • isCancel:跟踪是否处于取消录音状态(上滑)
  • recordTime:纪录录音时长(秒)
  • waveHeights:存储声波高度数组,用于实现波形动画
  • timerId:存储计时器ID,用于后续扫除
  • waveTimerId:存储波形动画计时器ID
  • touchStartY:纪录触摸起始位置,用于盘算上滑距离
  • cancelThreshold:定义上滑多少距离触发取消状态
三、添加底子方法

在实现UI交互前,我们先添加一些底子方法来处理录音状态和动画效果。
  1. @Entry
  2. @Component
  3. struct WeChatRecorder {
  4.   // 状态变量定义...
  5.   /**
  6.    * 开始录音,初始化状态及启动计时器
  7.    */
  8.   startRecording() {
  9.     this.isRecording = true
  10.     this.isCancel = false
  11.     this.recordTime = 0
  12.     // 启动计时器,每秒更新录音时长
  13.     this.timerId = setInterval(() => {
  14.       this.recordTime++
  15.     }, 1000)
  16.     // 启动波形动画计时器,随机更新波形高度
  17.     this.waveTimerId = setInterval(() => {
  18.       this.updateWaveHeights()
  19.     }, 200)
  20.   }
  21.   /**
  22.    * 结束录音,清理计时器和状态
  23.    */
  24.   stopRecording() {
  25.     // 清除计时器
  26.     if (this.timerId !== 0) {
  27.       clearInterval(this.timerId)
  28.       this.timerId = 0
  29.     }
  30.     if (this.waveTimerId !== 0) {
  31.       clearInterval(this.waveTimerId)
  32.       this.waveTimerId = 0
  33.     }
  34.     // 如果是取消状态,则显示取消提示
  35.     if (this.isCancel) {
  36.       console.info('录音已取消')
  37.     } else if (this.recordTime > 0) {
  38.       // 如果录音时长大于0,则模拟发送语音
  39.       console.info(`发送语音,时长: ${this.recordTime}秒`)
  40.     }
  41.     // 重置状态
  42.     this.isRecording = false
  43.     this.isCancel = false
  44.     this.recordTime = 0
  45.   }
  46.   /**
  47.    * 更新波形高度以产生动画效果
  48.    */
  49.   updateWaveHeights() {
  50.     // 创建新的波形高度数组
  51.     const newHeights = this.waveHeights.map(() => {
  52.       // 生成20-40之间的随机高度
  53.       return Math.floor(Math.random() * 20) + 20
  54.     })
  55.     this.waveHeights = newHeights
  56.   }
  57.   /**
  58.    * 格式化时间显示,将秒转换为"00:00"格式
  59.    */
  60.   formatTime(seconds: number): string {
  61.     const minutes = Math.floor(seconds / 60)
  62.     const secs = seconds % 60
  63.     return `${minutes.toString()
  64.       .padStart(2, '0')}:${secs.toString()
  65.       .padStart(2, '0')}`
  66.   }
  67.   aboutToDisappear() {
  68.     // 组件销毁时清除计时器
  69.     if (this.timerId !== 0) {
  70.       clearInterval(this.timerId)
  71.       this.timerId = 0
  72.     }
  73.     if (this.waveTimerId !== 0) {
  74.       clearInterval(this.waveTimerId)
  75.       this.waveTimerId = 0
  76.     }
  77.   }
  78.   build() {
  79.     // 之前的布局代码
  80.   }
  81. }
复制代码
在这一步中,我们实现了以下方法:

  • startRecording:开始录音,初始化状态并启动计时器
  • stopRecording:竣事录音,清理计时器和状态
  • updateWaveHeights:更新波形高度数组,产生动画效果
  • formatTime:将秒数格式化为"00:00"格式的时间显示
  • aboutToDisappear:组件销毁时清理计时器,防止内存泄漏
四、实现长按事件处理

接下来,我们为"按住 语言"按钮添加触摸事件处理,实现长按开始录音的功能。
  1. @Entry
  2. @Component
  3. struct WeChatRecorder {
  4.   // 之前的代码...
  5.   build() {
  6.     Column() {
  7.       Stack({ alignContent: Alignment.Center }) {
  8.         // 暂时留空,后面会添加录音界面
  9.       }
  10.       .layoutWeight(1)
  11.       
  12.       // 底部输入栏
  13.       Row() {
  14.         // 录音按钮
  15.         Text(this.isRecording ? '松开 发送' : '按住 说话')
  16.           .fontSize(16)
  17.           .fontColor(this.isRecording ? Color.White : '#333333')
  18.           .backgroundColor(this.isRecording ? '#07C160' : '#F5F5F5')
  19.           .borderRadius(4)
  20.           .textAlign(TextAlign.Center)
  21.           .width('100%')
  22.           .height(40)
  23.           .padding({ left: 10, right: 10 })
  24.           // 添加触摸事件
  25.           .onTouch((event) => {
  26.             if (event.type === TouchType.Down) {
  27.               // 按下时,记录起始位置,开始录音
  28.               this.touchStartY = event.touches[0].y
  29.               this.startRecording()
  30.             } else if (event.type === TouchType.Move) {
  31.               // 移动时,检测是否上滑到取消区域
  32.               const moveDistance = this.touchStartY - event.touches[0].y
  33.               this.isCancel = moveDistance > this.cancelThreshold
  34.             } else if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
  35.               // 松开或取消触摸时,结束录音
  36.               this.stopRecording()
  37.             }
  38.           })
  39.       }
  40.       .width('100%')
  41.       .backgroundColor(this.isRecording ? Color.Transparent : Color.White)
  42.       .expandSafeArea()
  43.       .padding({ left: 15, right: 15, top: 15 })
  44.       .border({ width: { top: 1 }, color: '#E5E5E5' })
  45.     }
  46.     .width('100%')
  47.     .height('100%')
  48.     .backgroundColor('#EDEDED')
  49.     .expandSafeArea()
  50.   }
  51. }
复制代码
在这一步中,我们:

  • 为按钮文本添加了动态内容,根据录音状态显示不同文字
  • 为按钮添加了触摸事件处理,包罗按下、移动和松开/取消
  • 根据录音状态动态改变底部栏的配景致
五、实现录音界面和声颠簸画

最后,我们添加录音状态下的界面显示,包罗上滑取消提示和声颠簸画。
  1. @Entry
  2. @Component
  3. struct WeChatRecorder {
  4.   // 之前的代码...
  5.   build() {
  6.     Column() {
  7.       // 聊天内容区域
  8.       Stack({ alignContent: Alignment.Center }) {
  9.         // 录音状态提示
  10.         if (this.isRecording) {
  11.           // 遮罩背景
  12.           Column()
  13.             .width('100%')
  14.             .height('100%')
  15.             .backgroundColor('#80000000')
  16.             .expandSafeArea()
  17.           Column() {
  18.             // 上滑取消提示
  19.             Text(this.isCancel ? '松开手指,取消发送' : '手指上滑,取消发送')
  20.               .fontSize(14)
  21.               .fontColor(this.isCancel ? Color.Red : '#999999')
  22.               .backgroundColor(this.isCancel ? '#FFE9E9' : '#D1D1D1')
  23.               .borderRadius(4)
  24.               .padding({
  25.                 left: 10,
  26.                 right: 10,
  27.                 top: 5,
  28.                 bottom: 5
  29.               })
  30.               .margin({ bottom: 20 })
  31.             // 录音界面容器
  32.             Column() {
  33.               // 声波动画容器
  34.               Row() {
  35.                 ForEach(this.waveHeights, (height: number, index) => {
  36.                   Column()
  37.                     .width(4)
  38.                     .height(height)
  39.                     .backgroundColor('#7ED321')
  40.                     .borderRadius(2)
  41.                     .margin({ left: 3, right: 3 })
  42.                 })
  43.               }
  44.               .width(160)
  45.               .height(100)
  46.               .justifyContent(FlexAlign.Center)
  47.               .margin({ bottom: 15 })
  48.               // 录音时间显示
  49.               Text(`${this.formatTime(this.recordTime)}`)
  50.                 .fontSize(16)
  51.                 .fontColor('#999999')
  52.             }
  53.             .width(180)
  54.             .backgroundColor(Color.White)
  55.             .borderRadius(8)
  56.             .justifyContent(FlexAlign.Center)
  57.             .padding(10)
  58.           }
  59.           .width('100%')
  60.           .height('100%')
  61.           .justifyContent(FlexAlign.Center)
  62.         }
  63.       }
  64.       .layoutWeight(1)
  65.       // 底部输入栏
  66.       // 与之前的代码相同
  67.     }
  68.     // 与之前的代码相同
  69.   }
  70. }
复制代码
在这一步中,我们添加了:

  • 录音状态下的遮罩配景,利用半透明玄色配景
  • 上滑取消提示,根据 isCancel 状态显示不同内容和样式
  • 声颠簸画容器,利用 ForEach 循环遍历 waveHeights 数组创建多个柱状条
  • 录音时间显示,利用 formatTime 方法格式化时间
六、完整实现

下面是完整的实当代码:
  1. /**
  2. * 微信语音录制动画效果
  3. * 实现功能:
  4. * 1. 长按按钮: 显示录音动画
  5. * 2. 上滑取消: 模拟取消录音
  6. * 3. 松开发送: 模拟发送语音
  7. */
  8. @Entry
  9. @Component
  10. struct WeChatRecorder {
  11.   // 是否正在录音
  12.   @State isRecording: boolean = false
  13.   // 是否显示取消提示(上滑状态)
  14.   @State isCancel: boolean = false
  15.   // 录音时长(秒)
  16.   @State recordTime: number = 0
  17.   // 声波高度变化数组
  18.   @State waveHeights: number[] = [20, 30, 25, 40, 35, 28, 32, 37]
  19.   // 计时器ID
  20.   private timerId: number = 0
  21.   // 波形动画计时器ID
  22.   private waveTimerId: number = 0
  23.   // 触摸起始位置
  24.   private touchStartY: number = 0
  25.   // 触摸移动阈值,超过该值显示取消提示
  26.   private readonly cancelThreshold: number = 50
  27.   /**
  28.    * 开始录音,初始化状态及启动计时器
  29.    */
  30.   startRecording() {
  31.     this.isRecording = true
  32.     this.isCancel = false
  33.     this.recordTime = 0
  34.     // 启动计时器,每秒更新录音时长
  35.     this.timerId = setInterval(() => {
  36.       this.recordTime++
  37.     }, 1000)
  38.     // 启动波形动画计时器,随机更新波形高度
  39.     this.waveTimerId = setInterval(() => {
  40.       this.updateWaveHeights()
  41.     }, 200)
  42.   }
  43.   /**
  44.    * 结束录音,清理计时器和状态
  45.    */
  46.   stopRecording() {
  47.     // 清除计时器
  48.     if (this.timerId !== 0) {
  49.       clearInterval(this.timerId)
  50.       this.timerId = 0
  51.     }
  52.     if (this.waveTimerId !== 0) {
  53.       clearInterval(this.waveTimerId)
  54.       this.waveTimerId = 0
  55.     }
  56.     // 如果是取消状态,则显示取消提示
  57.     if (this.isCancel) {
  58.       console.info('录音已取消')
  59.     } else if (this.recordTime > 0) {
  60.       // 如果录音时长大于0,则模拟发送语音
  61.       console.info(`发送语音,时长: ${this.recordTime}秒`)
  62.     }
  63.     // 重置状态
  64.     this.isRecording = false
  65.     this.isCancel = false
  66.     this.recordTime = 0
  67.   }
  68.   /**
  69.    * 更新波形高度以产生动画效果
  70.    */
  71.   updateWaveHeights() {
  72.     // 创建新的波形高度数组
  73.     const newHeights = this.waveHeights.map(() => {
  74.       // 生成20-40之间的随机高度
  75.       return Math.floor(Math.random() * 20) + 20
  76.     })
  77.     this.waveHeights = newHeights
  78.   }
  79.   /**
  80.    * 格式化时间显示,将秒转换为"00:00"格式
  81.    */
  82.   formatTime(seconds: number): string {
  83.     const minutes = Math.floor(seconds / 60)
  84.     const secs = seconds % 60
  85.     return `${minutes.toString()
  86.       .padStart(2, '0')}:${secs.toString()
  87.       .padStart(2, '0')}`
  88.   }
  89.   aboutToDisappear() {
  90.     // 组件销毁时清除计时器
  91.     if (this.timerId !== 0) {
  92.       clearInterval(this.timerId)
  93.       this.timerId = 0
  94.     }
  95.     if (this.waveTimerId !== 0) {
  96.       clearInterval(this.waveTimerId)
  97.       this.waveTimerId = 0
  98.     }
  99.   }
  100.   build() {
  101.     Column() {
  102.       // 聊天内容区域(模拟)
  103.       Stack({ alignContent: Alignment.Center }) {
  104.         // 录音状态提示
  105.         if (this.isRecording) {
  106.           // 遮罩背景
  107.           Column()
  108.             .width('100%')
  109.             .height('100%')
  110.             .backgroundColor('#80000000')
  111.             .expandSafeArea()
  112.           Column() {
  113.             // 上滑取消提示
  114.             Text(this.isCancel ? '松开手指,取消发送' : '手指上滑,取消发送')
  115.               .fontSize(14)
  116.               .fontColor(this.isCancel ? Color.Red : '#999999')
  117.               .backgroundColor(this.isCancel ? '#FFE9E9' : '#D1D1D1')
  118.               .borderRadius(4)
  119.               .padding({
  120.                 left: 10,
  121.                 right: 10,
  122.                 top: 5,
  123.                 bottom: 5
  124.               })
  125.               .margin({ bottom: 20 })
  126.             // 录音界面容器
  127.             Column() {
  128.               // 声波动画容器
  129.               Row() {
  130.                 ForEach(this.waveHeights, (height: number, index) => {
  131.                   Column()
  132.                     .width(4)
  133.                     .height(height)
  134.                     .backgroundColor('#7ED321')
  135.                     .borderRadius(2)
  136.                     .margin({ left: 3, right: 3 })
  137.                 })
  138.               }
  139.               .width(160)
  140.               .height(100)
  141.               .justifyContent(FlexAlign.Center)
  142.               .margin({ bottom: 15 })
  143.               // 录音时间显示
  144.               Text(`${this.formatTime(this.recordTime)}`)
  145.                 .fontSize(16)
  146.                 .fontColor('#999999')
  147.             }
  148.             .width(180)
  149.             .backgroundColor(Color.White)
  150.             .borderRadius(8)
  151.             .justifyContent(FlexAlign.Center)
  152.             .padding(10)
  153.           }
  154.           .width('100%')
  155.           .height('100%')
  156.           .justifyContent(FlexAlign.Center)
  157.         }
  158.       }
  159.       .layoutWeight(1)
  160.       // 底部输入栏
  161.       Row() {
  162.         // 录音按钮
  163.         Text(this.isRecording ? '松开 发送' : '按住 说话')
  164.           .fontSize(16)
  165.           .fontColor(this.isRecording ? Color.White : '#333333')
  166.           .backgroundColor(this.isRecording ? '#07C160' : '#F5F5F5')
  167.           .borderRadius(4)
  168.           .textAlign(TextAlign.Center)
  169.           .width('100%')
  170.           .height(40)
  171.           .padding({ left: 10, right: 10 })// 添加触摸事件
  172.           .onTouch((event) => {
  173.             if (event.type === TouchType.Down) {
  174.               // 按下时,记录起始位置,开始录音
  175.               this.touchStartY = event.touches[0].y
  176.               this.startRecording()
  177.             } else if (event.type === TouchType.Move) {
  178.               // 移动时,检测是否上滑到取消区域
  179.               const moveDistance = this.touchStartY - event.touches[0].y
  180.               this.isCancel = moveDistance > this.cancelThreshold
  181.             } else if (event.type === TouchType.Up || event.type === TouchType.Cancel) {
  182.               // 松开或取消触摸时,结束录音
  183.               this.stopRecording()
  184.             }
  185.           })
  186.       }
  187.       .width('100%')
  188.       .backgroundColor(this.isRecording ? Color.Transparent : Color.White)
  189.       .expandSafeArea()
  190.       .padding({ left: 15, right: 15, top: 15 })
  191.       .border({ width: { top: 1 }, color: '#E5E5E5' })
  192.     }
  193.     .width('100%')
  194.     .height('100%')
  195.     .backgroundColor('#EDEDED')
  196.     .expandSafeArea()
  197.   }
  198. }
复制代码
拓展与优化

以上是根本的实现,如果想进一步优化,可以思量:

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

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

  • HarmonyOS的ArkUI布局系统
  • 状态管理(@State)
  • 触摸事件处理
  • 定时器和动画
  • 条件渲染
  • 组件生命周期处理
这些技术和概念不仅适用于这个特定效果,还可以应用于各种交互设计中。渴望这个教程能帮助你更好地明白HarmonyOS开辟,并创建出更加精致的应用界面!

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

种地

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表