前言
在IM通讯软件中,基本上都会有emoji表情功能。聊天气泡中要显示文字和emoji表情的混排(下图所示),在原生iOS开发时,可以用富文本NSAttributedString实现,安卓中用SpannableString实现。当用到React-Native来开发这个功能的时候,貌似没有直接的现成的实现方案。经过一番努力,这个功能已经在项目中实现 ,在此记录。
思路
假设有一条信息在输入框是这样的:“假设[微笑]有一条信息[龇牙]是这样的”
[微笑],[龇牙]都是emoji表情的名称.
这条消息发送出去之后,要显示成如上图形式的样式。也就是要把emoji表情名称替换成相应图片。
这条消息发送出去之后,要显示成如上图形式的样式。也就是要把emoji表情名称替换成相应图片。
处理步骤如下:
1.运用正则表达式将表情名称识别出来,然后把这条文本截成数组:
[
{content:假设},
{resource:[微笑]},
{content:有一条信息},
{resource:[龇牙]},
{content:是这样的}
]
2.根据这个数组进行组件排列,key是content的话放置Text,key是resources的话放置Image,一字排开。
字符串转成数组
1.编写正确的正则表达式;
2.根据正则表达式匹配出所有的表情名称,把他们在字符串中的位置用数组记录;
3.根据记录位置的数组,截断字符串;
4.组成相应的数组。
代码:
形如“[微笑]”的字符串形式的正则表达式为:
代码中的正则方法:
注意后面有第二个参数‘g’,它表示全局匹配,把字符串中所有能匹配的都匹配一遍。
如果没有第二个参数,则会找到第一个匹配的字符串之后就停止。
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');
如果没有第二个参数,则会找到第一个匹配的字符串之后就停止。
根据数组排列组件
此步骤方法较多,可以用数组的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的滑动流程性不会产生影响,只是渲染的时候稍微有点慢。可接受。
以上是现在的解决方案,方法略显笨拙。如果哪位童鞋有更好的方法,请在下面留言。