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的滑动流程性不会产生影响,只是渲染的时候稍微有点慢。可接受。


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



  • 6
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 7
    评论
支持拖拽 复制 截图 excel ----------------------------------------------------------------------------------------------------------------------------------------------复制代码 /** * Created by zhanglei on 2017/5/23. */ import React, { Component, PropTypes } from 'react'; import { Icon,Modal,message } from 'antd'; import ContentEditable from 'react-contenteditable' import './edit.less' export default class Editor extends Component { static propTypes = { className: PropTypes.string, value:PropTypes.string, editColor:PropTypes.string, }; constructor(props){ super(props); ['inputTextChange','onchangefile','onpaste','ondrop','onParseOrDrop'].map(item=>this[item]=this[item].bind(this)); this.state={ value:null, tableData:[], linkModel:false, visible:false, isColor:false, myDisabled:false, isEdit:true, isFace:false, isBackground:false, linkValue:null, editStatus:[ {label:'加粗',value:'bold',icon:'zitijiacu'}, {label:'斜体',value:'italic',icon:'zitixieti'}, {label:'下划线',value:'underline',icon:'xiahuaxian'}, {label:'链接',value:'createLink',icon:'lianjie'} ], fontSizeData:[ {title:'大号',value:'h1',icon:'H1'}, {title:'中号',value:'h2',icon:'h2'}, {title:'正常',value:'h3',icon:'h3-copy-copy'}, {title:'小号',value:'h4',icon:'h4'} ], isSent:true, colorData:[ 'red','orange','yellow','#01FF01','#98F5FF','#8686FF','rgb(216, 154, 255)', '#fff', '#DE1607','#E49402','#E2E205','#04DE04','rgb(71, 237, 255)','#6363F9','rgb(204, 123, 255)', 'rgb(206, 205, 205)', '#C10303','#D08702','#C5C503','#07C307','rgb(0, 221, 245)','#4C4CFB','rgb(184, 70, 255)', 'rgb(183, 183, 183)', '#960505','#AB7005','#ABAB03','#02A902','rgb(6, 171, 189)','#3333FF','rgb(167, 25, 255)', 'rgb(148, 148, 148)', '#710303','#989805','#989805','#059C05','rgb(9, 138, 152)','blue','#A020F0', 'rgb(76, 75, 75)', '#5D0404',' #757504','#757504','green','rgb(2, 99, 109)','blue','#A020F0', '#000','rgb(56, 2, 2)' ], } }; componentDidMount(){ document.addEventListener('click',this.documentClick); }; componentWillReceiveProps(nextProps){ if('value' in nextProps&&this;.state.editValue !== nextProps.value){ this.setState({editValue:nextProps.value}) } } //全局取消隐藏颜色框 documentClick=(e)=>{ const {isColor,isBackground} = this.state; if(isColor||isBackground){ let en = e.srcElement||e.target; const name = '.color-content'; while(en){ if(en.className&&en;.className === name.replace('.','')){ return; } en = en[removed]; } this.setState({isColor:false,isBackground:false}); } }; //卸载颜色框 componentWillUnmount(){ document.removeEventListener('click',this.documentClick) } /* * <粘贴功能> * @param onParseOrDrop 通用方法

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值