小程序仿微信聊天按住说话功能
实现:按住说话有录音动画,上滑可取消发送,松开发送录音
录音授权判断
# .wxml
bind:longpress="onLongpress" catch:touchmove="onTouchMove">{{touchBtn?'松开 结束':'按住说话'}}
复制代码
// # ontouchstart
record() {
const scopeRecord = app.globalData.scopeRecord;
// 无授权时获取授权
if (!scopeRecord) {
getScopeRecord();
return;
}
this.data.touched = true;
// 触发父组件录音事件,由父组件调用录音API
this.triggerEvent('startRecord')
},
复制代码
上滑取消发送,利用滑动距离判断是否取消,
onTouchMove(e) {
if (e.touches[0].clientY < 520) {
// # 取消发送
this.data.cancelRecord = true;
wx.showToast({
title: '松开,取消发送',
duration: 10000,
image: '/page/common/resource/image/cancel.png'
})
} else {
// # 不取消
this.showMicAni(1);
this.data.cancelRecord = false;
}
},
复制代码
松开发送 - 快速点击bug
bug:快速点击按钮,一直显示录音中 原因:调起录音API需要一定时间,会出现touchstart->touchend->调起录音API,touchend事件在录音前触发了,无法再调起停止录音API
思路:可以在调用录音后用个变量a标识,在!a时执行touchend后则用变量b标识,在调用录音后若有b则再立即停止录音。但该方法涉及太多变量传递(父传子,子传父),就偷了下懒用了个400ms的延时
onTouchEnd(e) {
if (!this.data.touched) return;
this.data.touched = false;
wx.hideToast();
// 延时处理 防止录音未调起就触发停止
setTimeout(() => {
this.triggerEvent('stopRecord', {
// # 判断是否取消发送
send: !this.data.cancelRecord
});
}, 400);
}
},
复制代码
除了说话中,上滑取消外,还要加多个说话时间太短的提示
方案一:我用了小程序的longpress事件做判断,未触发该事件则属于说话太短;同时,如果在没在触发Longpress前触发了上滑也默认开启录音
方案二:touchstart时开始计时,touchend结束计时,用时间来判断
// # 利用长按判断录音是否太短
onLongpress() {
this.data.recording = true;
},
// # 上滑时默认录音中
onTouchMove(e) {
this.data.recording = true;
if (e.touches[0].clientY < 520) { ... } else { ... }
},
onTouchEnd(e) {
if (this.data.recording) {
// # 触发停止...
} else {
wx.showToast({
title: '说话时间太短',
duration: 500,
image: '/page/common/resource/image/warn-b.png'
})
// # 触发停止...
}
},
复制代码
录音动画 — 实现微信聊天录音中可根据音量大小改麦克风音量提示的动画的toast。
问题:官方API中录音回调没提供音量数据,无法根据音量来做动画。而且我们使用了wx.showToast,image无法使用动图,也无法自定义动画
思路:既然无法获取音量数据,又需要用户感知有在录音中,还无法做动画。我想了个方法,能不能每隔一段时间换一张image直到录音停止,而且更换图片的顺序不能是固定的,这样看起来很假,必须随机更换图片
注意:touchend和touchmove要清空定时器,touchmove手指回到按钮则要再次调用showMicAni
showMicAni(i) {
// # 初始化间隔时间
let delay = 500;
let that = this;
wx.showToast({
title: '上滑,取消发送',
duration: 60000,
// # 准备5张麦克风不同状态的png
image: '/page/common/resource/image/mic0' + i + '.png'
})
that.data.time1 = setTimeout(() => {
// util.getRandomInt公共方法,随机生成【1,5】中的整数
i = util.getRandomInt(1, 5)
that.showMicAni(i)
}, delay);
},
// touchstart
record() {
// # 开始录音...
this.showMicAni(1);
},
onTouchEnd() {
clearTimeout(this.data.time1)
},
onTouchMove(e) {
clearTimeout(this.data.time1)
if (e.touches[0].clientY < 520) {
// # 取消发送...
} else {
this.showMicAni(1);
this.data.cancelRecord = false;
}
},
复制代码
以下是完整代码
// # .js
import wlogin from '../../../common/resource/js/login.js';
import util from '../../../../utils/util'
let app = getApp();
const getScopeRecord = wlogin.recordInpower();
Component({
properties: {},
data: {
touchBtn: false,
},
ready() {},
methods: {
// 利用长按判断录音是否太短
onLongpress() {
this.data.recording = true;
},
// touchstart
record() {
const scopeRecord = app.globalData.scopeRecord;
if (!scopeRecord) {
getScopeRecord();
return;
}
this.data.recording = false;
this.data.touched = true;
this.data.cancelRecord = false;
this.setData({
touchBtn: true
})
this.triggerEvent('startRecord')
this.showMicAni(1);
},
showMicAni(i) {
let delay = 500;
let that = this;
wx.showToast({
title: '上滑,取消发送',
duration: 60000,
image: '/page/common/resource/image/mic0' + i + '.png'
})
that.data.time1 = setTimeout(() => {
// 随机生成
i = util.getRandomInt(1, 5)
that.showMicAni(i)
}, delay);
},
onTouchMove(e) {
this.data.recording = true;
clearTimeout(this.data.time1)
if (e.touches[0].clientY < 520) {
this.data.cancelRecord = true;
wx.showToast({
title: '松开,取消发送',
duration: 10000,
image: '/page/common/resource/image/cancel.png'
})
} else {
this.showMicAni(1);
this.data.cancelRecord = false;
}
},
onTouchEnd(e) {
clearTimeout(this.data.time1)
if (this.data.recording) {
if (!this.data.touched) return;
this.data.touched = false;
wx.hideToast();
setTimeout(() => {
this.triggerEvent('stopRecord', {
send: !this.data.cancelRecord
});
}, 400);
} else {
wx.showToast({
title: '说话时间太短',
duration: 500,
image: '/page/common/resource/image/warn-b.png'
})
// 延时处理 防止录音未调起就触发停止
setTimeout(() => {
this.triggerEvent('stopRecord', {send: false});
}, 400);
}
this.setData({touchBtn: false})
},
})
复制代码