前言


在IM通讯软件中,基本上都会有emoji表情功能。聊天气泡中要显示文字和emoji表情的混排(下图所示),在原生iOS开发时,可以用富文本NSAttributedString实现,安卓中用SpannableString实现。当用到React-Native来开发这个功能的时候,貌似没有直接的现成的实现方案。经过一番努力,这个功能已经在项目中实现 ,在此记录。




emoji表情正则匹配 emoji表情搭配_富文本




思路


假设有一条信息在输入框是这样的:“假设[微笑]有一条信息[龇牙]是这样的”


[微笑],[龇牙]都是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是一个对象,形如:

emoji表情正则匹配 emoji表情搭配_图文混排_02



总结

这个方法存在一个问题,一个表情用一个Image控件显示,如果表情太多则会影响性能。经过实测,50个表情显示,对ListView的滑动流程性不会产生影响,只是渲染的时候稍微有点慢。可接受。


以上是现在的解决方案,方法略显笨拙。如果哪位童鞋有更好的方法,请在下面留言。