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

news/2025/3/15 22:07:01/文章来源:https://www.cnblogs.com/Megasu/p/18774266

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

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

最终效果预览

我们将实现以下功能:

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

image

一、基础布局实现

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

@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]// 计时器IDprivate timerId: number = 0// 波形动画计时器IDprivate waveTimerId: number = 0// 触摸起始位置private touchStartY: number = 0// 触摸移动阈值,超过该值显示取消提示private readonly cancelThreshold: number = 50build() {// 之前的布局代码}
}

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

  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 = truethis.isCancel = falsethis.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 = falsethis.isCancel = falsethis.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 % 60return `${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].ythis.startRecording()} else if (event.type === TouchType.Move) {// 移动时,检测是否上滑到取消区域const moveDistance = this.touchStartY - event.touches[0].ythis.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]// 计时器IDprivate timerId: number = 0// 波形动画计时器IDprivate waveTimerId: number = 0// 触摸起始位置private touchStartY: number = 0// 触摸移动阈值,超过该值显示取消提示private readonly cancelThreshold: number = 50/*** 开始录音,初始化状态及启动计时器*/startRecording() {this.isRecording = truethis.isCancel = falsethis.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 = falsethis.isCancel = falsethis.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 % 60return `${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].ythis.startRecording()} else if (event.type === TouchType.Move) {// 移动时,检测是否上滑到取消区域const moveDistance = this.touchStartY - event.touches[0].ythis.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开发,并创建出更加精美的应用界面!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/899405.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot使用Kafka生产者、消费者

SpringBoot使用Kafka生产者、消费者@目录依赖配置文件生产者消费者 依赖 <!--kafka--> <dependency><groupId>org.springframework.kafka</groupId><artifactId>spring-kafka</artifactId><version>xxx</version> </depend…

jasperreport 使用和问题记录

引言:之前一直没有记录的习惯,所以在这里记录有些是回忆,所以可能有些不连贯.而且本人的问题不一定是你的问题,仅作参考. (注:本人是使用java生成pdf文件) 1.介绍(只介绍自己用的到) 进入软件 File->new->jasper report->blank A4 我们得到了一张A4纸,如下图所示.(注:…

Java学习四大名著:《Java核心技术 卷II:高级特性(原书第12版)》 | PDF免费下载

《Java核心技术 卷II:高级特性(原书第12版)》是Java学习的经典书籍,该书深入探讨了 Java 语言的高级功能,涵盖流与文件处理、并发编程、网络、数据库、JVM 调优等关键主题。适合有一定基础的开发者,帮助他们掌握高效编程技巧,优化性能,并深入理解 Java 生态系统,是进阶…

STM32CubeIDE 下载与安装教程(以 1.18.0 版本为例)

STM32CubeIDE 下载与安装教程(以 1.18.0 版本为例) 软件介绍 STM32CubeIDE 是 STMicroelectronics 提供的官方集成开发环境(IDE),专为 STM32 微控制器系列设计。它融合了 STM32CubeMX 和 Eclipse 的强大功能,提供了一个完整的开发环境,适用于嵌入式软件开发和调试。STM3…

【作业3】结对项目:实现一个自动生成小学四则运算题目的命令行程序

⭐成员:3223004473詹艺珏 and 3223004301吴梦琪 📎Github链接:https://github.com/Jue610/Jue610/tree/main/ArithProbelm这个作业属于哪个课程 23软件工程这个作业要求在哪里 【作业3】结对项目这个作业的目标 实现一个自动生成小学四则运算题目的命令行程序,培养团…

用ESP32做一个遥控机械臂

RC汽车机械臂只是一个有轮子的RC汽车。该机械臂是一个四自由度机械臂,这意味着它有四个运动部件。我使用mg90伺服电机,因为它们是金属的,但塑料sg90也应该工作。如下图所示。我使用了两个独立的电源,每个都由2S锂离子(Li-on)电池组成,一个用于MCU,另一个用于电机。这是为…

最小化安装Ubuntu

最小化安装 前言: 有时候需要搭建虚拟机,每一次都需要去找文章,搞小半天才能完成环境搭建 这一次写一篇文章记录一下,目的是以后能比较快速简单的搭建好环境 概要 最小化安装需要手动启用网卡 安装防火墙 ⇒ 打开某些端口确保ssh连接等 安装openssh-server openssh-client ⇒ …

愤怒的小鸟

Day 2025/2/20愤怒的小鸟剪切精灵图-改sprite mode为mutiple在sprite editor中slice设置弹弓与鸟的层级关系layer-player 鸟加springjoint组件-distance-0.3-autodistance关闭弹弓组件的一些基本属性:Distance:两点之间的固定距离(设定完有剩余的距离就是可拉伸的长度)Freq…

通过振动传感器,触发水的运动并将其转换为声音

“微挑战”是在巴塞罗那IAAC的“紧急未来设计硕士”课程中为期一周的工作坊。在这一周,我们有时间、空间和专业人士的支持来创建一个功能原型,它也可以是一个投机性的人工制品,有助于我们的个人研究和实践。这个原型应该基于迭代和使用:数字制造工具、生物制造、人工智能、…

单链表练习与重下AS

1.练习题:我的代码答案: /*** Definition for singly-linked list.* struct ListNode {* int val;* struct ListNode *next;* };*/ struct ListNode* reverseList(struct ListNode* head) {struct ListNode dummpy;struct ListNode* tmp;dummpy.next=NULL;//引入临时…

pycharm连接autodl服务器

昨天听舍友说,他们第一个实验都跑完了,瞬间焦虑起来,原来落后这么多,完事昨天晚上7点开始看教程,配环境,看了好几个教程,每个教程都不一样竟然!然后问舍友,舍友说:你先知道你要跑什么项目,我说:我知道啊,我要做什么项目,想要什么结果,我都知道,项目和代码都有,…

shell脚本报错:test.sh: line 2: $\r: command not found

问题 在win上写好shell脚本,传到linux服务器运行调试的时候报错:test.sh: line 2: $\r: command not found 原因 这个错误是由于脚本文件的换行符问题引起的。Windows和Linux系统的换行符不同:Windows使用\r\n(回车+换行)。 Linux使用\n(换行)。脚本是在Windows上编辑的…