ReactNative實現emoji表情圖文混排方案

前言

在IM通訊軟件中,基本上都會有emoji表情功能。聊天氣泡中要顯示文字和emoji表情的混排(下圖所示),在原生iOS開發時,可以用富文本NSAttributedString實現,安卓中用SpannableString實現。當用到React-Native來開發這個功能的時候,貌似沒有直接的現成的實現方案。經過一番努力,這個功能已經在項目中實現 ,在此記錄。



思路

假設有一條信息在輸入框是這樣的:“假設[微笑]有一條信息[齜牙]是這樣的”
[微笑],[齜牙]都是emoji表情的名稱.
這條消息發送出去之後,要顯示成如上圖形式的樣式。也就是要把emoji表情名稱替換成相應圖片。

處理步驟如下:
1.運用正則表達式將表情名稱識別出來,然後把這條文本截成數組:
[
  {content:假設},
  {resource:[微笑]},
  {content:有一條信息},
  {resource:[齜牙]},
  {content:是這樣的}
]
2.根據這個數組進行組件排列,key是content的話放置Text,key是resources的話放置Image,一字排開。

字符串轉成數組

1.編寫正確的正則表達式;
2.根據正則表達式匹配出所有的表情名稱,把他們在字符串中的位置用數組記錄;
3.根據記錄位置的數組,截斷字符串;
4.組成相應的數組。

代碼:
export function stringToContentArray(text) {
    //text = "wwww[微笑]eeee[鬼臉]asdfasfasd[大笑]w222";
    var regex = new RegExp('\\[[a-zA-Z0-9\\/\\u4e00-\\u9fa5]+\\]', 'g');
    var contentArray = [];
    var regArray = text.match(regex);
    console.log(regArray);
    if (regArray === null) {
        contentArray.push({"Content" : text});
        return contentArray;
    }

    var indexArray = [];
    var pos = text.indexOf(regArray[0]);//頭
    for (let i = 1; i < regArray.length; i++) {
        indexArray.push(pos);
        pos = text.indexOf(regArray[i],pos + 1);
    }
    indexArray.push(pos);//尾

    console.log("indexArray = ",indexArray);
    for (let i=0; i<indexArray.length; i++) {
        if (indexArray[i] === 0) {//一開始就是表情
            contentArray.push({"Resources" : EMOTION_GIF_NAME[regArray[i]],attr: {Type:"0"}});
        } else {
            if (i === 0) {
                contentArray.push({"Content" : text.substr(0,indexArray[i])});
            } else {
                if (indexArray[i] - indexArray[i-1] - regArray[i-1].length > 0) {//兩個表情相鄰,中間不加content
                    contentArray.push({"Content" : text.substr(indexArray[i-1] + regArray[i-1].length,indexArray[i] - indexArray[i-1] - regArray[i-1].length)});
                }
            }
            contentArray.push({"Resources" : EMOTION_GIF_NAME[regArray[i]],attr: {Type:"0"}});
        }
    }

    let lastLocation = indexArray[indexArray.length - 1] + regArray[regArray.length - 1].length;
    if (text.length > lastLocation) {
        contentArray.push({"Content": text.substr(lastLocation,text.length - lastLocation)});
    }
    return contentArray;
}

形如“[微笑]”的字符串形式的正則表達式爲:
'\\[[a-zA-Z0-9\\/\\u4e00-\\u9fa5]+\\]'
代碼中的正則方法:
var regex =new RegExp('\\[[a-zA-Z0-9\\/\\u4e00-\\u9fa5]+\\]','g');
注意後面有第二個參數‘g’,它表示全局匹配,把字符串中所有能匹配的都匹配一遍。
如果沒有第二個參數,則會找到第一個匹配的字符串之後就停止。

根據數組排列組件

此步驟方法較多,可以用數組的map實現。貼上一段代碼

render() {
   return (
      <View style={[styles[this.props.position].container, this.props.containerStyle[this.props.position]]}>
       
          {
            this.props.currentMessage.contentArray.map((content, i) => {
            if (content["Content"] != null || !EMOTION_PATH[content["Resources"].toLowerCase()]) {//文本  
                return (
                  <Text
                    key={i}
                    style={[styles[this.props.position].text, this.props.textStyle[this.props.position],{textAlign:'left'}]}
                    adjustsFontSizeToFit={true}
                    minimumFontScale={1.0}
                  >
                    {content["Content"] != null? content["Content"] : content["Resources"]}
                  </Text>
                );
              } else if (content["Resources"] != null) {//emoji
              let w = this._emojiWidth;
              return (
                <Image
                  key={i}
                  style={[customStyle.emoji, { "width": w, "height": w }]}
                  source={EMOTION_PATH[content["Resources"].toLowerCase()]}
                >
                </Image>
              );
            }
          })
        }
      </View>
    );
  }

上面代碼中有EMOTION_PATH[content["Resources"],
這句是把表情名稱轉換成表情的圖片路徑,EMOTION_PATH是一個對象,形如:

總結

這個方法存在一個問題,一個表情用一個Image控件顯示,如果表情太多則會影響性能。經過實測,50個表情顯示,對ListView的滑動流程性不會產生影響,只是渲染的時候稍微有點慢。可接受。


以上是現在的解決方案,方法略顯笨拙。如果哪位童鞋有更好的方法,請在下面留言。



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章