React Native自定义View解析Emoji

作者: Ivor0057 | 来源:发表于2016-12-07 15:08 被阅读1244次

    一、需求准备

    在react native的类中实现可以解析多种字符格式的内容并放入到指定文本中。效果图如下:

    emoji_example_1

    二、Emoji封装

    将需要对应好的Emoji表情图片放到指定文件夹,然后写一个公共的Const.js封装成一个对象,实现变量与图片资源的关系映射:

    export const emojiReflection = {
        // emoji表情对应关系
        "[微笑]": require('../emojiImage/emoji_1.png'),
        "[呲牙]": require('../emojiImage/emoji_2.png'),
        "[偷笑]": require('../emojiImage/emoji_3.png'),
        ……
    }
    

    三、生命周期方法的简单处理

    这里只针对于子文本的展示,实际效果是将该文本放到一个ListView的item中,其功能实现无大差异,只需在外层包一个ListView即可。所以这里只需将这个文本放入一个Text标签中。
      这里加入了对文本高度的判断,超过了4行只展示4行,并展示出“更多”按钮,由于设置了行高为20,只需在onLayout( )方法中对高度设置监听处理即可。

    return (
      <View>
        <Text key={'textMore'} style={{ lineHeight: 20, maxHeight: this.state.rowHeight }}
          onLayout={this.state.textState == 1 ? (e) => {
            let {x, y, width, height} = e.nativeEvent.layout;
            if (Math.ceil(height / 20) > 4 && this.state.textState == 1) {
              this.setState({ textState: 2, rowHeight: 80 });
            } else {
              this.setState({ rowHeight: height });
            }
          } : null}>
          {this.state.Views}
        </Text>
        {showMore}
      </View>
    );
    

    其中,这里的this.state.Views是一个数组,用于存放截取处理后的各个子View。可在constructor( )或componentWillMount( )方法中定义:

    constructor() {
        super();
        this.state = {
            rowHeight: 10000,
            textState: 1,
            Views: [],
        }
    }
    

    这里,在componentWillMount( )方法中接收指定字符串textContent(当然你完全可以自定义一个你想要的String):

    componentWillMount() {
        let textContent = this.props.textContent;
        this.matchContentString(textContent);
    }
    

    匹配的逻辑与思路

    这里用到的是正则匹配的方法,先定义出三种匹配规则,即匹配[微笑]格式的emoji表情、匹配@...格式的文本、匹配http://www.baidu.com 格式的网址。
      先使用正则定义好匹配方法:

    let emojiReg = '\\[[^\\]]+\\]';
    let nameReg = '@([^\\s@]+)';
    let httpReg = '((https://|http://|www\.|ftp://)[A-Za-z0-9\._\?%&;:+\-=/#]*)';
    let emojiAt = new RegExp(emojiReg);
    let nameAt = new RegExp(nameReg);
    let httpAt = new RegExp(httpReg);
    

    然后开始写一个方法,实现自己的思路:将一个字符串按三种匹配规则匹配,得到3个index,取最小者,将这个字符串拆分成三部分,即0-index,index,index-end。然后0-index部分直接返回一个文本,index部分再分别处理,然后将最后的index-end部分实现递归,直到最终的index为-1,即只剩下纯文本为止。

    matchContentString(textContent) {
        // 匹配得到3个index并放入数组中
        let emojiIndex = textContent.search(emojiAt);
        let nameIndex = textContent.search(nameAt);
        let httpIndex = textContent.search(httpAt);
        let checkIndexArray = [];
        // 若匹配不到,则直接返回一个全文本
        if (emojiIndex === -1 && nameIndex === -1 && httpIndex === -1) {
            let emptyTextView = (<Text key ={'emptyTextView'+(Math.random()*100)}>{textContent}</Text>);
            this.state.Views.push(emptyTextView);
        } else {
          if (emojiIndex !== -1) checkIndexArray.push(emojiIndex);
          if (nameIndex !== -1) checkIndexArray.push(nameIndex);
          if (httpIndex !== -1) checkIndexArray.push(httpIndex);
          // 取index最小者
          let minIndex = Math.min.apply(Math, checkIndexArray);
          // 将0-index部分返回文本
          let firstTextView = (<Text key ={'firstTextView'+(Math.random()*100)}>{textContent.substring(0, minIndex)}</Text>);
          this.state.Views.push(firstTextView);
          // 将index部分作分别处理
          switch (minIndex) {
            case emojiIndex:
              this.matchEmojiString(textContent.substring(minIndex));
              break;
            case nameIndex:
              this.matchNameString(this.props.ats, this.props.atDeparts, textContent.substring(minIndex));
              break;
            case httpIndex:
              this.matchHttpString(textContent.substring(minIndex));
              break;
            default:
              break;
          }
        }
    }
    

    如上代码,可看到,每执行一次比较处理index的思路就会将原文本拆分成三部分,然后再分别处理。这样的定位index会比较明确。下面就再实现一下具体的三种处理方式:

    【处理带emoji表情的】

    matchEmojiString(emojiStr) {
    
        let castStr = emojiStr.match(emojiAt);
        let emojiLength = castStr[0].length;
        let imageView = (<Image key={emojiStr} style={{ width: 15, height: 14 }} source={emojiReflection[castStr]} />);
        this.state.Views.push(imageView);
        this.matchContentString(emojiStr.substring(emojiLength));
    
    }
    

    【处理带@...文本的】

    matchNameString(ats, atDeparts, nameStr) {
    
        let castStr = nameStr.match(nameAt);
        let nameString = castStr[0];
        let atUser = null;
        let atMyDeparts = null;
        if (ats && ats.length > 0) {
          atUser = ats[0];
        }
        if (atUser == null && atDeparts && atDeparts.length > 0) {
          atMyDeparts = atDeparts[0];
        }
    
        let nameView = (<CustomTextView key={'name' + nameStr} textContent={nameString} contentType={'name'} atUser={atUser} atDeparts={atMyDeparts} />);
        this.state.Views.push(nameView);
        this.matchContentString(nameStr.substring(nameString.length));
    
    }
    

    【处理带http:...网址的】

    matchHttpString(httpStr) {
    
        let castStr = httpStr.match(httpAt);
        let httpString = castStr[0];
        let httpView = (<CustomTextView key={'http'+httpString} textContent={httpString} contentType={'http'} />);
        this.state.Views.push(httpView);
        this.matchContentString(httpStr.substring(httpString.length));
    
    }
    

    其中CustomTextView只是一个简单的自定义View,只实现如跳转链接,改变字体颜色等功能,完全可以自定义,在这里就不贴代码了。
      最终,一个满足三种匹配的自定义View就实现啦,看下效果咯~


    emoji_example_2

    相关文章

      网友评论

      • KongQi:写下一些自己模仿完的总结:
        1:最外层中不要用View包裹,用Text来包裹,Text可以嵌套,可以解决如果第一段文字过长,表情换行的问题
        2:那种 短文字+表情+长英文,长英文不足后面的空间会换行,系统会认为是一个单词给单独换行,如果后面跟着是中文或者短英文则没有问题
      • K_Gopher:请问有Demo吗
        Ivor0057:@SunnyEver0 可以试试截取字符串,对整个父View的宽度进行计算,然后处理,如:加个ellipse这种结尾省略的方式。
        SunnyEver0:请问楼主遇到过这种问题吗,我加了maxWidth和flexwWarp属性实现自动换行,我这边会遇到这个问题,输入框输入短文字+表情+长文字,类似 a +【大笑】+ bcdefghijklmnopqrst 由于第三块长文字已经超出当前行的最大宽度,执行了自动换行,所以a+【大笑】后面会露出一大块空白。请问对于这种问题有没有什么好的解决方法呢
        Ivor0057:代码都贴出来了~~~
      • 9ac64e1f7a99:有意思,不错
        Ivor0057:@岁月__痕迹 嘿嘿

      本文标题:React Native自定义View解析Emoji

      本文链接:https://www.haomeiwen.com/subject/lfidmttx.html