React Native自定義View解析Emoji

一、需求準(zhǔn)備

在react native的類中實(shí)現(xiàn)可以解析多種字符格式的內(nèi)容并放入到指定文本中。效果圖如下:

emoji_example_1

二、Emoji封裝

將需要對(duì)應(yīng)好的Emoji表情圖片放到指定文件夾,然后寫一個(gè)公共的Const.js封裝成一個(gè)對(duì)象,實(shí)現(xiàn)變量與圖片資源的關(guān)系映射:

export const emojiReflection = {
    // emoji表情對(duì)應(yīng)關(guān)系
    "[微笑]": require('../emojiImage/emoji_1.png'),
    "[呲牙]": require('../emojiImage/emoji_2.png'),
    "[偷笑]": require('../emojiImage/emoji_3.png'),
    ……
}

三、生命周期方法的簡單處理

這里只針對(duì)于子文本的展示,實(shí)際效果是將該文本放到一個(gè)ListView的item中,其功能實(shí)現(xiàn)無大差異,只需在外層包一個(gè)ListView即可。所以這里只需將這個(gè)文本放入一個(gè)Text標(biāo)簽中。
  這里加入了對(duì)文本高度的判斷,超過了4行只展示4行,并展示出“更多”按鈕,由于設(shè)置了行高為20,只需在onLayout( )方法中對(duì)高度設(shè)置監(jiān)聽處理即可。

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是一個(gè)數(shù)組,用于存放截取處理后的各個(gè)子View??稍赾onstructor( )或componentWillMount( )方法中定義:

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

這里,在componentWillMount( )方法中接收指定字符串textContent(當(dāng)然你完全可以自定義一個(gè)你想要的String):

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

匹配的邏輯與思路

這里用到的是正則匹配的方法,先定義出三種匹配規(guī)則,即匹配[微笑]格式的emoji表情、匹配@...格式的文本、匹配http://www.baidu.com 格式的網(wǎng)址。
  先使用正則定義好匹配方法:

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);

然后開始寫一個(gè)方法,實(shí)現(xiàn)自己的思路:將一個(gè)字符串按三種匹配規(guī)則匹配,得到3個(gè)index,取最小者,將這個(gè)字符串拆分成三部分,即0-index,index,index-end。然后0-index部分直接返回一個(gè)文本,index部分再分別處理,然后將最后的index-end部分實(shí)現(xiàn)遞歸,直到最終的index為-1,即只剩下純文本為止。

matchContentString(textContent) {
    // 匹配得到3個(gè)index并放入數(shù)組中
    let emojiIndex = textContent.search(emojiAt);
    let nameIndex = textContent.search(nameAt);
    let httpIndex = textContent.search(httpAt);
    let checkIndexArray = [];
    // 若匹配不到,則直接返回一個(gè)全文本
    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;
      }
    }
}

如上代碼,可看到,每執(zhí)行一次比較處理index的思路就會(huì)將原文本拆分成三部分,然后再分別處理。這樣的定位index會(huì)比較明確。下面就再實(shí)現(xiàn)一下具體的三種處理方式:

【處理帶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:...網(wǎng)址的】

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只是一個(gè)簡單的自定義View,只實(shí)現(xiàn)如跳轉(zhuǎn)鏈接,改變字體顏色等功能,完全可以自定義,在這里就不貼代碼了。
  最終,一個(gè)滿足三種匹配的自定義View就實(shí)現(xiàn)啦,看下效果咯~


emoji_example_2
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 173,560評(píng)論 25 708
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 134,993評(píng)論 19 139
  • 概述 iOS系統(tǒng)相機(jī)、相冊(cè)功能全部依托于圖像選取控制器UIImagePickerController,在使用該控制...
    蚊香醬閱讀 4,853評(píng)論 3 48
  • 表白就是冒著以后連朋友都不能做的危險(xiǎn)去賭以后能正大光明牽你手擁抱你愛著你的機(jī)會(huì) ...
    劉皓永遠(yuǎn)都是劉皓閱讀 262評(píng)論 0 0
  • 2.28 火 早上起床還蠻早,結(jié)果還是8點(diǎn)多還在匆匆準(zhǔn)備出門,去到公司對(duì)桌的同事看到我說,你忘記畫眉了,馬上想著向...
    ancilapple閱讀 220評(píng)論 0 0