小程序即时通讯聊天控件(二)
来源:互联网 发布:法国留学知乎 编辑:程序博客网 时间:2024/05/16 12:08
小程序即时通讯——文本、语音输入控件(二)实现原理
集成请看小程序即时通讯聊天控件(一)集成
这个控件的编写主要分为三个部分:文本和语音信息的输入及获取
、录音时手势操作的处理
、自定义功能
。其中文本信息的输入及获取
使用微信官方控件input即可实现,语音输入
也是,可以参考录音功能。这部分就是调用个API的事儿,不讲述了。文本和语音状态的切换
、自定义功能
的实现原理也非常简单,这里可以简单讲下。倒是手势操作
这块花了一些时间,调试过程中也发现了一些问题,可以跟大家分享下。
先说下输入状态的切换吧
点击左侧的按钮会切换输入的状态
我把这个控件中用到的所有字段都用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
- 小程序即时通讯聊天控件(二)
- 小程序即时通讯聊天控件(一)
- 集成环信即时通讯的Android聊天小程序
- 聊天程序(二)
- IOS开发,XMPP实现聊天,即时通讯(二)
- Java学习笔记(二)-------客户端一对多(TCP)多人聊天小程序
- 网络编程学习笔记(二)UDP协议及聊天小程序的实现
- JGroups使用范例(聊天小程序)
- java小聊天程序
- java聊天小程序
- udp聊天小程序
- 在线聊天小程序
- Java聊天小程序
- 聊天小程序
- 一个聊天小程序
- java聊天小程序
- Android即时通讯--仿QQ即时聊天:(二)闪屏页及登录页面的实现
- Erlang聊天程序后端(二)
- FHost ‘192.168.21.70’ is not allowed to connect to this MySQL serverConnection closed by foreign hos
- 详解JAVA垃圾回收机制
- 装饰模式
- 关于 Goroutine 的一些使用细节
- sql查询
- 小程序即时通讯聊天控件(二)
- LVM实用指南
- NotePad++快捷键总结
- 函数时间计算——函数作为另一个函数的参数使用
- 商机 | 大数据/政务云采购清单 招标7起,最高招标价为503.9万(11.4-11.7)
- 什么是multipart/form-data请求
- 开源NoSQL数据库:ArangoDB 入门指南
- meituan
- 大数据早报:国产新一代人工智能芯片发布 「龙猫数据」获3370万元A轮融资(11.8)