小程序即時通訊

小程序即時通訊——文本、語音輸入控件(一)集成

2018-07-31 作者公告:現在擁有聊天列表UI的項目已經在當前的github倉庫中更新了?。?!目前還需要一段時間來將多個模塊從業(yè)務上拆分,現在可以在stage-1.0分支預覽。點擊前往:聊天列表地址 集成方式文檔還沒寫完。。。

聊天輸入組件

近期一直在做微信小程序,業(yè)務上要求在小程序里實現即時通訊的功能。這部分功能需要用到文本和語音輸入及一些語音相關的手勢操作。所以我寫了一個控件來處理這些操作。

控件樣式

image.png

我們先來看下效果 (現在新增了右下角發(fā)送按鈕?。≡谳斎肟颢@取到焦點后,右下角會顯示發(fā)送按鈕!!只不過沒有更新圖片。。。)


小程序.gif

目前的功能就是動態(tài)圖中展示的,我們可以使用這個控件來切換輸入方式(文本或語音)、獲取到輸入的信息、取消語音輸入、語音消息錄制過短過長的判斷(該接口暫時還未開放),支持發(fā)送圖片和其他自定義拓展內容。(語音和圖片發(fā)送失敗是因為小程序新版模擬器的問題,真機上沒事)。

注意:本文所講的SDK中不包含列表的展示部分及發(fā)送狀態(tài)部分,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',//發(fā)送按鈕的背景色
            sendButtonTextColor: 'white',//發(fā)送按鈕的文本顏色
            extraArr: [{
                picName: 'choose_picture',
                description: '照片'
            }, {
                picName: 'take_photos',
                description: '拍攝'
            }, {
                picName: 'close_chat',
                description: '自定義功能'
            }],
            tabbarHeigth: 48
        });
  • page:這個是指當前的page。
  • systemInfo:必填。手機的系統(tǒng)信息,用于控件的適配。
    -minVoiceTime: 最小錄音時長,秒,小于該值則彈出‘說話時間太短’
    -maxVoiceTime: 最大錄音時長,秒,填寫的數值大于該值則按60秒處理。錄音時長如果超過該值,則會保存最大時長的錄音,并彈出‘說話時間超時’并終止錄音
    -startTimeDown: 開始倒計時時間,秒,錄音時長達到該值時彈窗界面更新為倒計時彈窗
  • extraArr:非必填。點擊右側加號時,顯示的自定義功能。picName元素的名字就是對應image/chat/extra文件夾下的png格式的圖片名稱,用于展示自定義功能的圖片。description元素用于展示自定義功能的文字說明。
  • tabbarHeight:非必填。這個也是用于適配。如果你的小程序有tabbar,那么需要填寫這個字段,填48就行。如果你的小程序沒有tabbar,那么就不要填寫這個字段。原因我會在第二篇講到。
  • format: 錄音格式,有效值:mp3或aac,僅在基礎庫1.6.0及以上生效,低版本不生效
  • sendButtonBgColor: 發(fā)送按鈕的背景色
  • sendButtonTextColor: 發(fā)送按鈕的文本顏色
3. 監(jiān)聽獲取輸入的信息

在初始化控件之后,監(jiān)聽信息的輸入,即可獲取到指定類型的信息

文本信息:
//文本信息的輸入監(jiān)聽
chatInput.setTextMessageListener(function (e) {
            let content = e.detail.value;//輸入的文本信息

        });  
語音信息:
//獲取錄音之后的音頻臨時文件
chatInput.recordVoiceListener(function (res, duration) {
         let tempFilePath = res.tempFilePath;//語音臨時文件的路徑
         let vDuration = duration;//錄音時長
     });
//監(jiān)聽錄音狀態(tài)
 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語法來實現,該控件也會進行模塊化。最快是一周時間,最慢是兩周,集成篇和進階篇會分別更新在這兩篇文章中。使用時開啟微信開發(fā)工具的ES6轉ES5即可。請大家耐心等待。

集成請看小程序即時通訊聊天控件(一)集成
這個控件的編寫主要分為三個部分:文本和語音信息的輸入及獲取、錄音時手勢操作的處理自定義功能。其中文本信息的輸入及獲取使用微信官方控件input即可實現,語音輸入也是,可以參考錄音功能。這部分就是調用個API的事兒,不講述了。文本和語音狀態(tài)的切換、自定義功能的實現原理也非常簡單,這里可以簡單講下。倒是手勢操作這塊花了一些時間,調試過程中也發(fā)現了一些問題,可以跟大家分享下。

先說下輸入狀態(tài)的切換吧

image.png

點擊左側的按鈕會切換輸入的狀態(tài)

我把這個控件中用到的所有字段都用inputObj這個對象來管理,在調用chatInput.init(page,opt)方法時就在pagedata對象中初始化了inputObj對象。那么是怎么點擊左側的按鈕切換狀態(tài)的呢?

沒錯!就是在點擊時將inputObj.inputStatus這個字段置為textvoice,然后渲染到布局上,布局使用wx:if來控制對應控件的顯示和隱藏。

上代碼:

chat-input.wxml(布局的代碼貼上來排版太亂了,所以我貼代碼的時候刪除了一些樣式。。。把最主要的放了出來)

<!--左側輸入狀態(tài)的圖片切換-->
<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)執(zhí)行時會調用
//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. 劃分手勢操作區(qū)域:

首先我們要搭建錄音功能的UI,包括底部的錄音按鈕和錄音時顯示的彈出窗。按鈕的適配很簡單,彈出窗的大小和位置也是,但是手勢操作向上滑動到一定范圍時,需要更新錄音的狀態(tài)為將要取消錄音,滑動的區(qū)域的限定肯定要通過js來實現,那么就需要引入systemInfo了,引入之后可以在初始化時就配置好這些信息。

具體實現是在chat-input.js,代碼如下:
chat-input.js

//這里的windowWidth、windowHeight是在初始化時從systemInfo中獲取到的。
function initVoiceData() {
    let width = windowWidth / 2.6;
    _page.setData({
        'inputObj.inputStatus': 'text',//初始狀態(tài)為文本輸入模式
        'inputObj.windowHeight': windowHeight,//注入可操作區(qū)域的總高度
        'inputObj.windowWidth': windowWidth,//注入可操作區(qū)域的總寬度
        'inputObj.voiceObj.status': 'end',//錄音為結束狀態(tài)
        'inputObj.voiceObj.startStatus': 0,//錄音按鈕狀態(tài),0:按住說話狀態(tài);1:松開結束狀態(tài)
        'inputObj.voiceObj.voicePartWidth': width,//錄音時彈出窗的寬度
        'inputObj.voiceObj.moveToCancel': false,//是否移動到了取消錄音區(qū)域
        'inputObj.voiceObj.voicePartPositionToBottom': (windowHeight - width / 2.4) / 2,//錄音時彈出窗的距離底部的位置,因為彈出窗是正方形的,所以這里是減的width,后面除的2.4是為了調整彈窗在屏幕中的顯示位置
        'inputObj.voiceObj.voicePartPositionToLeft': (windowWidth - width) / 2//錄音時彈出窗的距離左側位置
    });
    cancelLineYPosition = windowHeight * 0.12;//向上滑動到距離屏幕底部cancelLineYPosition大小時,進入到了取消錄音的區(qū)域
}

chat-input.wxml中引入了語音模板voice

<import src="voice.wxml" />
<template is="voice" data="{{voiceObj:inputObj.voiceObj}}" />

語音功能具體的布局voice.wxml,其實也是通過js中的setData動態(tài)的去更新UI,沒什么技術含量。
稍微有些技術含量的就是下面代碼中button綁定的兩個事件(手指在屏幕上移動和離開)的處理。
我們需要在手指移動時判斷觸摸點的位置是否已經到了取消區(qū)域(在距離底部cancelLineYPosition大小以上的位置都是取消區(qū)域),在手指離開屏幕時判斷錄音是否過短。

image.png

<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="錄音時顯示的動態(tài)圖" 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?'松開手指,取消發(fā)送':'手指上滑,取消發(fā)送'):(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) {//如果移出了該區(qū)域
                _page.setData({
                    'inputObj.voiceObj.moveToCancel': false
                })
            }
        }

    }
};

e.touches[0].clientY是當前觸摸點的Y軸坐標,正數。小程序的坐標原點在左上角,所以當從下往上滑動時,該參數的值會越來越小。那么我們由windowHeight + tabbarHeigth - e.touches[0].clientY就可以計算出用戶滑動屏幕時,以左下角為原點的Y軸坐標了。
將這個y值與cancelLineYPosition進行比較就可以得到用戶是否滑到了取消區(qū)域。

那么有人會問,為什么要加個tabbarHeigth呢?這是為了解決微信tabbar的一個bug。開發(fā)過程中我發(fā)現無論是Android還是iOS手機,當有你的小程序有tabbar時,通過wx.getSystemInfo()wx.getSystemInfoSync()獲取到的windowHeight值都偏小,恰好缺少一個tabbar的高度。如果不適配的話,會導致一些手機向上滑動還沒滑出button的范圍,程序就標識為inputObj.voiceObj.moveToCancel = true的狀態(tài)了。

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長按事件)觸發(fā)時調用的,當錄音結束后,就會回調wx.startRecord()success。

下面的這段代碼是在_page.long$click$voice$btn中執(zhí)行的,用于錄音結束后獲取文件臨時路徑。

wx.startRecord({
    success: function (res) {
        if (_page.data.inputObj.voiceObj.status === 'short') {//錄音時間太短或者移動到了取消錄音區(qū)域, 則取消錄音
            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);
    }
});

總結

這個組件到此講完了,在開發(fā)過程中,我將語音、自定義功能各個模塊在UI上分離開了,以便后期的維護。其實大部分時間都花在了UI上。手勢操作那部分做過移動開發(fā)的同學們相信都會有思路,也沒有難度。寫的有不好的地方,或者大家有什么問題,可以在評論區(qū)留言,我會繼續(xù)改進。
github下載https://github.com/unmagic/wechat-im

出處:https://blog.csdn.net/sinat_27612147/article/details/78456363

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內容