鸿蒙特效教程02-微信语音录制动画效果实现教程
本教程适合HarmonyOS初学者,通过简单到复杂的步骤,一步步实现类似微信APP中的语音录制动画效果。
最终效果预览
我们将实现以下功能:
- 长按"按住说话"按钮:显示录音界面和声波动画
- 录音过程中显示实时时长
- 手指上滑:取消录音发送
- 松开手指:根据状态发送或取消录音
一、基础布局实现
首先,我们需要创建基本的界面布局,模拟微信聊天界面的结构。
ts
@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()
}
}
这一步我们创建了一个基本的聊天界面布局,包含两部分:
- 顶部聊天内容区域:使用Stack布局,目前为空
- 底部输入栏:包含一个"按住 说话"按钮
二、添加状态变量
接下来,我们需要添加一些状态变量来跟踪录音状态和动画效果。
ts
@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() {
// 之前的布局代码
}
}
我们添加了以下状态变量:
isRecording
:跟踪是否正在录音isCancel
:跟踪是否处于取消录音状态(上滑)recordTime
:记录录音时长(秒)waveHeights
:存储声波高度数组,用于实现波形动画timerId
:存储计时器ID,用于后续清除waveTimerId
:存储波形动画计时器IDtouchStartY
:记录触摸起始位置,用于计算上滑距离cancelThreshold
:定义上滑多少距离触发取消状态
三、添加基础方法
在实现UI交互前,我们先添加一些基础方法来处理录音状态和动画效果。
ts
@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() {
// 之前的布局代码
}
}
在这一步中,我们实现了以下方法:
startRecording
:开始录音,初始化状态并启动计时器stopRecording
:结束录音,清理计时器和状态updateWaveHeights
:更新波形高度数组,产生动画效果formatTime
:将秒数格式化为"00:00"格式的时间显示aboutToDisappear
:组件销毁时清理计时器,防止内存泄漏
四、实现长按事件处理
接下来,我们为"按住 说话"按钮添加触摸事件处理,实现长按开始录音的功能。
ts
@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()
}
}
在这一步中,我们:
- 为按钮文本添加了动态内容,根据录音状态显示不同文字
- 为按钮添加了触摸事件处理,包括按下、移动和松开/取消
- 根据录音状态动态改变底部栏的背景色
五、实现录音界面和声波动画
最后,我们添加录音状态下的界面显示,包括上滑取消提示和声波动画。
ts
@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)
// 底部输入栏
// 与之前的代码相同
}
// 与之前的代码相同
}
}
在这一步中,我们添加了:
- 录音状态下的遮罩背景,使用半透明黑色背景
- 上滑取消提示,根据
isCancel
状态显示不同内容和样式 - 声波动画容器,使用
ForEach
循环遍历waveHeights
数组创建多个柱状条 - 录音时间显示,使用
formatTime
方法格式化时间
六、完整实现
下面是完整的实现代码:
ts
/**
* 微信语音录制动画效果
* 实现功能:
* 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()
}
}
拓展与优化
以上是基本的实现,如果想进一步优化,可以考虑:
- 真实的录音功能:使用HarmonyOS的媒体录制API实现实际录音
- 声音波形实时变化:根据实际录音音量调整波形高度
- 振动反馈:在录音开始、取消或发送时添加振动反馈
- 显示已录制的语音消息:将录制好的语音添加到聊天消息列表中
- 录音时长限制:添加最长录音时间限制(如微信的60秒)
总结
通过这个教程,我们从零开始实现了类似微信的语音录制动画效果。主要用到了以下技术:
- HarmonyOS的ArkUI布局系统
- 状态管理(@State)
- 触摸事件处理
- 定时器和动画
- 条件渲染
- 组件生命周期处理
这些技术和概念不仅适用于这个特定效果,还可以应用于各种交互设计中。希望这个教程能帮助你更好地理解HarmonyOS开发,并创建出更加精美的应用界面!