小程序即时通讯——文本、语音输入控件(一)集成
2018-07-31 作者公告:现在拥有聊天列表UI的项目已经在当前的github仓库中更新了!!!目前还需要一段时间来将多个模块从业务上拆分,现在可以在stage-1.0分支预览。点击前往:聊天列表地址 集成方式文档还没写完。。。
聊天输入组件
近期一直在做微信小程序,业务上要求在小程序里实现即时通讯的功能。这部分功能需要用到文本和语音输入及一些语音相关的手势操作。所以我写了一个控件来处理这些操作。
控件样式
image.png我们先来看下效果 (现在新增了右下角发送按钮!!在输入框获取到焦点后,右下角会显示发送按钮!!只不过没有更新图片。。。)
小程序.gif
目前的功能就是动态图中展示的,我们可以使用这个控件来切换输入方式(文本或语音)、获取到输入的信息、取消语音输入、语音消息录制过短过长的判断(该接口暂时还未开放),支持发送图片和其他自定义拓展内容。(语音和图片发送失败是因为小程序新版模拟器的问题,真机上没事)。
注意:本文所讲的SDK中不包含列表的展示部分及发送状态部分,SDK只测试过微信基础库1.4.0及以上版本。
这部分内容我会从集成、编写控件两个部分来讲解。毕竟大部分人都是想尽快集成来着,所以先说说集成部分。
集成
一、导入SDK相关文件
导入SDK时一定要用图中所示的路径,不然的话你就自己挨个修改wxml和wxss里面的文件路径哈。
图片文件总共大概是45kb,chat-input文件夹总共大概41kb。这就是SDK所有的文件,一共在90kb左右。
二、集成到聊天页面
1. 在聊天页面中导入chat-input文件
- 在聊天页的js文件中导入
let chatInput = require('../../modules/chat-input/chat-input');
- 在聊天页的wxml文件中引入布局
<import src="../../modules/chat-input/chat-input.wxml"/>
<template is="chat-input" data="{{inputObj:inputObj,textMessage:textMessage,showVoicePart:true}}"/>
- 在聊天页的wxss文件中引入样式表
@import "../../modules/chat-input/chat-input.wxss";
根据你的路径来导入这些内容
2. 初始化chatInput
chatInput.init(page, {
systemInfo: wx.getSystemInfoSync(),
minVoiceTime: 1,//秒,最小录音时长,小于该值则弹出‘说话时间太短’
maxVoiceTime: 60,//秒,最大录音时长,大于该值则按60秒处理
startTimeDown: 56,//秒,开始倒计时时间,录音时长达到该值时弹窗界面更新为倒计时弹窗
format:'mp3',//录音格式,有效值:mp3或aac,仅在基础库1.6.0及以上生效,低版本不生效
sendButtonBgColor: 'mediumseagreen',//发送按钮的背景色
sendButtonTextColor: 'white',//发送按钮的文本颜色
extraArr: [{
picName: 'choose_picture',
description: '照片'
}, {
picName: 'take_photos',
description: '拍摄'
}, {
picName: 'close_chat',
description: '自定义功能'
}],
tabbarHeigth: 48
});
-
page
:这个是指当前的page。 -
systemInfo
:必填。手机的系统信息,用于控件的适配。
-minVoiceTime
: 最小录音时长,秒,小于该值则弹出‘说话时间太短’
-maxVoiceTime
: 最大录音时长,秒,填写的数值大于该值则按60秒处理。录音时长如果超过该值,则会保存最大时长的录音,并弹出‘说话时间超时’并终止录音
-startTimeDown
: 开始倒计时时间,秒,录音时长达到该值时弹窗界面更新为倒计时弹窗 -
extraArr
:非必填。点击右侧加号时,显示的自定义功能。picName
元素的名字就是对应image/chat/extra
文件夹下的png格式
的图片名称,用于展示自定义功能的图片。description
元素用于展示自定义功能的文字说明。 -
tabbarHeight
:非必填。这个也是用于适配。如果你的小程序有tabbar,那么需要填写这个字段,填48就行。如果你的小程序没有tabbar,那么就不要填写这个字段。原因我会在第二篇讲到。 -
format
: 录音格式,有效值:mp3或aac,仅在基础库1.6.0及以上生效,低版本不生效 -
sendButtonBgColor
: 发送按钮的背景色 -
sendButtonTextColor
: 发送按钮的文本颜色
3. 监听获取输入的信息
在初始化控件之后,监听信息的输入,即可获取到指定类型的信息
文本信息:
//文本信息的输入监听
chatInput.setTextMessageListener(function (e) {
let content = e.detail.value;//输入的文本信息
});
语音信息:
//获取录音之后的音频临时文件
chatInput.recordVoiceListener(function (res, duration) {
let tempFilePath = res.tempFilePath;//语音临时文件的路径
let vDuration = duration;//录音时长
});
//监听录音状态
chatInput.setVoiceRecordStatusListener(function (status) {
switch (status) {
case chatInput.VRStatus.START://开始录音
break;
case chatInput.VRStatus.SUCCESS://录音成功
break;
case chatInput.VRStatus.CANCEL://取消录音
break;
case chatInput.VRStatus.SHORT://录音时长太短
break;
case chatInput.VRStatus.UNAUTH://未授权录音功能
break;
case chatInput.VRStatus.FAIL://录音失败(已经授权了)
break;
}
})
自定义功能:
//收起自定义功能窗口
chatInput.closeExtraView();
//自定义功能点击事件
chatInput.clickExtraListener(function (e) {
let itemIndex = parseInt(e.currentTarget.dataset.index);//点击的自定义功能索引
if (itemIndex === 2) {
that.myFun();//其他的自定义功能
return;
}
//选择图片或拍照
wx.chooseImage({
count: 1, // 默认9
sizeType: ['compressed'],
sourceType: itemIndex === 0 ? ['album'] : ['camera'],
success: function (res) {
let tempFilePath = res.tempFilePaths[0];
}
});
});
//新增右下角加号button点击事件
chatInput.setExtraButtonClickListener(function (dismiss) {
console.log('Extra弹窗是否消失', dismiss);
})
至此,输入组件SDK的集成就完成了!
github地址https://github.com/unmagic/wechat-im
小程序即时通讯——文本、语音输入控件(二)实现原理
转载请注明出处:https://blog.csdn.net/sinat_27612147/article/details/78476241
2018-07-30 作者公告:关于大家非常关心的列表UI部分,我近期会更新一期列表UI的控件和教程。我会使用ES6语法来实现,该控件也会进行模块化。最快是一周时间,最慢是两周,集成篇和进阶篇会分别更新在这两篇文章中。使用时开启微信开发工具的ES6转ES5即可。请大家耐心等待。
集成请看小程序即时通讯聊天控件(一)集成
这个控件的编写主要分为三个部分:文本和语音信息的输入及获取
、录音时手势操作的处理
、自定义功能
。其中文本信息的输入及获取
使用微信官方控件input即可实现,语音输入
也是,可以参考录音功能。这部分就是调用个API的事儿,不讲述了。文本和语音状态的切换
、自定义功能
的实现原理也非常简单,这里可以简单讲下。倒是手势操作
这块花了一些时间,调试过程中也发现了一些问题,可以跟大家分享下。
先说下输入状态的切换吧
image.png点击左侧的按钮会切换输入的状态
我把这个控件中用到的所有字段都用inputObj
这个对象来管理,在调用chatInput.init(page,opt)
方法时就在page
的data
对象中初始化了inputObj
对象。那么是怎么点击左侧的按钮切换状态的呢?
没错!就是在点击时将inputObj.inputStatus
这个字段置为text
或voice
,然后渲染到布局上,布局使用wx:if
来控制对应控件的显示和隐藏。
上代码:
chat-input.wxml
(布局的代码贴上来排版太乱了,所以我贴代码的时候删除了一些样式。。。把最主要的放了出来)
<!--左侧输入状态的图片切换-->
<image src="../../image/chat/voice/{{inputObj.inputStatus==='voice'?'keyboard':'voice'}}.png"
bindtap="changeInputWayEvent" />
<!--控制显示语音输入-->
<block wx:if="{{inputObj.inputStatus==='voice'}}">
<template is="voice" data="{{voiceObj:inputObj.voiceObj}}" />
</block>
<!--控制显示文本输入-->
<input wx:if="{{inputObj.inputStatus==='text'}}"
confirm-type="send" value="{{textMessage}}" bindconfirm="chatInputSendTextMessage" />
chat-input.js
//在chat-input.wxml中绑定的changeInputWayEvent事件
//该方法在chatInput.init(page,opt)执行时会调用
//inputObj.extraObj.chatInputShowExtra这个字段是用于在点击切换按钮时隐藏自定义功能弹窗
function initChangeInputWayEvent() {
_page.changeInputWayEvent = function () {
_page.setData({
'inputObj.inputStatus': _page.data.inputObj.inputStatus === 'text' ? 'voice' : 'text',
'inputObj.extraObj.chatInputShowExtra': false
});
}
}
小技巧:在调用_page.setData()
时,如果传入整个inputObj
对象,会刷新布局中与inputObj
有关的所有UI,但是如果传入inputObj
的某个元素(如inputObj.inputStatus
),则只会刷新与该元素有关的所有UI,同时会减少渲染时数据的传入量,从而实现局部刷新,增加渲染速度。
自定义功能与上面的实现方式的思路是一样的。就不再赘述了。
下面重点说下手势操作部分
1. 划分手势操作区域:
首先我们要搭建录音功能的UI,包括底部的录音按钮和录音时显示的弹出窗。按钮的适配很简单,弹出窗的大小和位置也是,但是手势操作向上滑动到一定范围时,需要更新录音的状态为将要取消录音
,滑动的区域的限定肯定要通过js来实现,那么就需要引入systemInfo了,引入之后可以在初始化时就配置好这些信息。
具体实现是在chat-input.js,代码如下:
chat-input.js
//这里的windowWidth、windowHeight是在初始化时从systemInfo中获取到的。
function initVoiceData() {
let width = windowWidth / 2.6;
_page.setData({
'inputObj.inputStatus': 'text',//初始状态为文本输入模式
'inputObj.windowHeight': windowHeight,//注入可操作区域的总高度
'inputObj.windowWidth': windowWidth,//注入可操作区域的总宽度
'inputObj.voiceObj.status': 'end',//录音为结束状态
'inputObj.voiceObj.startStatus': 0,//录音按钮状态,0:按住说话状态;1:松开结束状态
'inputObj.voiceObj.voicePartWidth': width,//录音时弹出窗的宽度
'inputObj.voiceObj.moveToCancel': false,//是否移动到了取消录音区域
'inputObj.voiceObj.voicePartPositionToBottom': (windowHeight - width / 2.4) / 2,//录音时弹出窗的距离底部的位置,因为弹出窗是正方形的,所以这里是减的width,后面除的2.4是为了调整弹窗在屏幕中的显示位置
'inputObj.voiceObj.voicePartPositionToLeft': (windowWidth - width) / 2//录音时弹出窗的距离左侧位置
});
cancelLineYPosition = windowHeight * 0.12;//向上滑动到距离屏幕底部cancelLineYPosition大小时,进入到了取消录音的区域
}
chat-input.wxml
中引入了语音模板voice
<import src="voice.wxml" />
<template is="voice" data="{{voiceObj:inputObj.voiceObj}}" />
语音功能具体的布局voice.wxml
,其实也是通过js中的setData
动态的去更新UI,没什么技术含量。
稍微有些技术含量的就是下面代码中button
绑定的两个事件(手指在屏幕上移动和离开)的处理。
我们需要在手指移动时判断触摸点的位置是否已经到了取消区域(在距离底部cancelLineYPosition大小以上的位置都是取消区域),在手指离开屏幕时判断录音是否过短。
<template name="voice">
<!--这里是最主要的一部分,button绑定了三个事件:屏幕长按、移动和离开,手势操作部分是后面两个事件来处理的-->
<button bind:longtap="long$click$voice$btn" catch:touchmove="send$voice$move$event" catch:touchend="send$voice$move$end$event" id="send$voice$btn" hover-class="btn-voice-press">{{voiceObj.startStatus?'松开 结束':'按住 说话'}}
</button>
<view wx:if="{{voiceObj.showCancelSendVoicePart}}"
style="width: {{voiceObj.voicePartWidth}}px;height: {{voiceObj.voicePartWidth}}px;display: flex;position: fixed;left: {{voiceObj.voicePartPositionToLeft}}px;bottom: {{voiceObj.voicePartPositionToBottom}}px;justify-content:center;align-items: center;border-radius: 20rpx;">
<view style="background-color:black;opacity:{{voiceObj.status==='timeDown'?0.6:0}};width: 100%;height: 100%;border-radius: 20rpx;position: absolute"/>
<image src="./../../image/chat/voice/{{voiceObj.status==='start'?(voiceObj.moveToCancel?'recall':'speak'):'attention'}}.png" style="width: 100%;height: 100%;border-radius: 20rpx" wx:if="{{voiceObj.status!=='timeDown'}}"/>
<text style="margin-bottom:30rpx;font-size: 150rpx;text-align: center;color: white;position: relative" wx:if="{{voiceObj.status==='timeDown'}}">{{voiceObj.timeDownNum}}</text>
<view class="voice-record-git-status-style" wx:if="{{!voiceObj.moveToCancel&&voiceObj.status!=='short'}}">
<image src="录音时显示的动态图" class="voice-record-git-size-style"/>
</view>
<text class="voice-status-style" style="background-color: {{voiceObj.moveToCancel?'#ab1900':'transparent'}};">{{voiceObj.status==='start'||voiceObj.status==='timeDown'?(voiceObj.moveToCancel?'松开手指,取消发送':'手指上滑,取消发送'):(voiceObj.status==='short'?'说话时间太短':'说话时间超时')}}</text>
</view>
</template>
2. 移动事件的处理。
_page.send$voice$move$event = function (e) {
if ('send$voice$btn' === e.currentTarget.id) {
let y = windowHeight + tabbarHeigth - e.touches[0].clientY;
if (y > cancelLineYPosition) {
if (!inputObj.voiceObj.moveToCancel) {
_page.setData({
'inputObj.voiceObj.moveToCancel': true
});
}
} else {
if (inputObj.voiceObj.moveToCancel) {//如果移出了该区域
_page.setData({
'inputObj.voiceObj.moveToCancel': false
})
}
}
}
};
e.touches[0].clientY
是当前触摸点的Y轴坐标,正数。小程序的坐标原点在左上角,所以当从下往上滑动时,该参数的值会越来越小。那么我们由windowHeight + tabbarHeigth - e.touches[0].clientY
就可以计算出用户滑动屏幕时,以左下角为原点的Y轴坐标了。
将这个y
值与cancelLineYPosition
进行比较就可以得到用户是否滑到了取消区域。
那么有人会问,为什么要加个tabbarHeigth
呢?这是为了解决微信tabbar
的一个bug。开发过程中我发现无论是Android还是iOS手机,当有你的小程序有tabbar时,通过wx.getSystemInfo()
或wx.getSystemInfoSync()
获取到的windowHeight
值都偏小,恰好缺少一个tabbar
的高度。如果不适配的话,会导致一些手机向上滑动还没滑出button的范围,程序就标识为inputObj.voiceObj.moveToCancel = true
的状态了。
3. 当用户手指离开屏幕时该怎么处理呢?
当然是要结束录音啦!在结束之前需要先判断下本次的录音时长是否小于最小的录音时长,然后再将用于记录录音时长的计时器关掉。
_page.send$voice$move$end$event = function (e) {
console.log('离开', e);
if ('send$voice$btn' === e.currentTarget.id) {
console.log('时间短', singleVoiceTimeCount, minVoiceTime);
if (singleVoiceTimeCount < minVoiceTime) {//语音时间太短
_page.setData({
'inputObj.voiceObj.status': 'short'
});
delayDismissCancelView();
} else {//语音时间正常
_page.setData({
'inputObj.voiceObj.showCancelSendVoicePart': false,
'inputObj.voiceObj.status': 'end'
});
}
if (timer) {//关闭计时器
clearInterval(timer);
}
endRecord();
}
}
录音文件的获取是在wx.startRecord()
方法的success
回调函数中接收,我是在long$click$voice$btn
(button长按事件)触发时调用的,当录音结束后,就会回调wx.startRecord()
的success
。
下面的这段代码是在_page.long$click$voice$btn
中执行的,用于录音结束后获取文件临时路径。
wx.startRecord({
success: function (res) {
if (_page.data.inputObj.voiceObj.status === 'short') {//录音时间太短或者移动到了取消录音区域, 则取消录音
typeof startVoiceRecordCbOk === "function" && startVoiceRecordCbOk(status.SHORT);
return;
} else if (_page.data.inputObj.voiceObj.moveToCancel) {
typeof startVoiceRecordCbOk === "function" && startVoiceRecordCbOk(status.CANCEL);
return;
}
console.log('录音成功');
typeof startVoiceRecordCbOk === "function" && startVoiceRecordCbOk(status.SUCCESS);
typeof sendVoiceCbOk === "function" && sendVoiceCbOk(res, singleVoiceTimeCount + '');
},
fail:res=>{
typeof startVoiceRecordCbOk === "function" && startVoiceRecordCbOk(status.FAIL);
typeof sendVoiceCbError === "function" && sendVoiceCbError(res);
}
});
总结
这个组件到此讲完了,在开发过程中,我将语音、自定义功能各个模块在UI上分离开了,以便后期的维护。其实大部分时间都花在了UI上。手势操作那部分做过移动开发的同学们相信都会有思路,也没有难度。写的有不好的地方,或者大家有什么问题,可以在评论区留言,我会继续改进。
github下载https://github.com/unmagic/wechat-im
出处:https://blog.csdn.net/sinat_27612147/article/details/78456363
网友评论