文章概述

今天的文章比较贴合界面设计部分,在demo中我们已经实现了一个业务组件——聊天输入框,那么本文将教大家聊天输入框的业务实现以及如何修改调整聊天输入框

聊天输入框的实现

1.样式分析

根据聊天输入框的俩种模式下,我们可以将样式划分为以下2种

  1. 文本模式:
  2. 语音模式:

实际上demo中提供的聊天输入框组件已经覆盖着俩种模式,包括输入文件,发送表情,长按说话,上滑取消等操作。当然如果我们要掌握好这个组件,我们也得分析一下组件中是的代码逻辑。

2. 代码分析

从整体上来说,整个demo工程中将组件进行了解耦合的设计,各个组件文件对应关系如下

由于聊天输入框的组件是ChatInputBox.vue,因此我们着重分析一下该文件中的代码即可。

1. data数据结构

data () {
let sysInfo = uni.getSystemInfoSync()
  return {
    ios: sysInfo.platform.toLowerCase() == 'ios',
    pageHeight: sysInfo.windowHeight,
    text: '',
    showText: '',
    focus: false,
    speechMode: false,
    faceMode: false,
    extraMode: false,
    speechIng: false,
    hoverOnSpeechCancelBtn: false
  }
},

从data中的数据结构而言我们不难看出,根据speechMode,faceMode,extraMode切换文本,语音,表情,扩展等操作模式的变化,我们对应的来看一下界面中的代码。

2. 界面控制模式切换

在界面中通过speechMode切换显示文本输入框与语音按钮,从而实现语音与文本的切换操作

<image
  @click="clickSpeech"
  class="chat-input-btn is-item"
  :src="!speechMode ? '../static/icon_btn_speech.png'
                    : '../static/icon_btn_keyboard.png'"
></image>
<view v-if="!speechMode"
  :class="[
    'is-item',
    'chat-input-this',
    ios ? '' : 'chat-input-this-isAndroid'
  ].join(' ')"
>
  <textarea
    ref="input"
    class="chat-input-this-elem"
    :value="showText"
    :focus="focus"
    :autofocus="focus"
    @blur="focus = false"
    @touchend="onInputFocus"
    @input="onTextareaInput"
    :adjust-position="false"
    auto-height
  />
</view>
<view
  v-else
  @touchstart.prevent="touchOnSpeech"
  @touchend="touchOffSpeech"
  @touchmove="touchMoveSpeech"
  class="is-item chat-input-this chat-input-speech-btn"
>
  <text class="chat-input-speech-btn-inner">按住说话</text>
</view>
<image
  class="chat-input-btn is-item"
  src="../static/icon_btn_face.png"
  @click="clickFaceBtn"
></image>
<image
  v-if="!text"
  class="chat-input-btn is-item"
  src="../static/icon_btn_more.png"
  @click="clickExtra"
></image>
<text
  v-else
  class="chat-send-btn is-item"
  @click="clickSend"
>发送</text>
</view>

3. 语音聊天的覆盖层实现

比较特别的地方是语音聊天有一个“说话中”的覆盖层,我们通过在template的最后追加一个语音聊天覆盖层,通过监听speechMode是否为true控制显隐,从而实现语音聊天的效果

<view v-if="speechIng" class="speech-fixed">
 <view></view>
 <view
   class="speech-fixed__time"
 >
   <image
     class="speech-fixed__time-icon"
     :src="
       hoverOnSpeechCancelBtn ? '/static/icon_cancel_record.png'
                              : '/static/recording.gif'
     "
     mode="widthFix"
   ></image>
   <text
     class="speech-fixed__time-text"
   >{{ hoverOnSpeechCancelBtn ? '手指上滑 取消发送'
                              : (speechIng.time > 50000 ? `剩余 ${60 - (speechIng.time / 1000).toFixed(0)} 秒` : '松开手指 取消发送') }}</text>
 </view>
 <view></view>
</view>

2.上滑取消语音的算法

一般而言用户在长按说话的时候,很难去做点击取消按钮这类操作,因此对于取消语音一般采用的是上滑取消语音的操作,而对于组件而言,内部实现长按时候手指移动算法如下
首先我们需要在界面上监听触摸事件,该事件的监听在vue/nvue下都能得到一个统一的反馈,只是nvue下对于y轴的坐标计算需要做一个负值纠正的处理。

      <view
        @touchstart.prevent="touchOnSpeech"
        @touchend="touchOffSpeech"
        @touchmove="touchMoveSpeech"
        class="is-item chat-input-this chat-input-speech-btn"
      >
        <text class="chat-input-speech-btn-inner">按住说话</text>
      </view>

touchOnSpeech主要是记录当前为长按事件,做好其他UI控件的事件冲突处理,也标记开始录音。

      async touchOnSpeech () {
        this.speechIng = { time: 0, timer: null }
        this.speechIng.timer = setInterval(e => {
          this.speechIng && (this.speechIng.time += 500);
          // 这里是超时判断
          if (this.speechIng.time >= 60000) {
            this.hoverOnSpeechCancelBtn = false
            this.touchOffSpeech()
          }
        }, 500)
        console.log('speech-start')
        let success = await this.$imUtils.startRecord()
        if (!success) {
          this.touchOffSpeech()
          uni.showToast({
            icon: 'none',
            position: 'bottom',
            title: '录音失败,请检查是否授权麦克风权限'
          })
        }
      }

touchOffSpeech主要是记录当前松开长按事件,从而做结束/取消录音的判断,这里使用了来自lodash的防抖处理,因为nvue下有可能会多次触发

      touchOffSpeech: _.debounce(async function () {
        if (!this.speechIng) {
          return
        }
        clearInterval(this.speechIng.timer)
        let timeLen = this.speechIng.time
        this.speechIng = null
        if (this.hoverOnSpeechCancelBtn) {
          this.hoverOnSpeechCancelBtn = false
          return
        }
        if (timeLen < 1000) {
          return
        }
        let filePath = await this.$imUtils.endRecord()
        if (!filePath) {
          return
        }
        this.$emit('sendAudio', { filePath, timeLen })
      }, 500, { leading: true, trailing: false }),

touchMoveSpeech主要是计算当前手指移动位置,如果到达了取消区域则设置取消状态为true,从而实现取消语音的处理。

        touches = touches[0]
        let minScope = 0
        let maxScope = this.pageHeight - 50
        // 这里我们默认只要离开了【长按说话】按钮就属于取消语音的处理,开发者可以根据实际需求调整业务逻辑
        if (touches.screenY >= minScope && touches.screenY <= maxScope) {
          this.hoverOnSpeechCancelBtn = true
        } else {
          this.hoverOnSpeechCancelBtn = false
        }

项目开源地址及交流群

项目成品效果查看:请点击项目引言
项目开源地址:https://gitee.com/ckong/Zhimi...
Uniapp开发交流群:755910061

03-06 00:06