directory

First, demand second, implementation ideas

The resources

A, requirements,

The author is working on a live broadcast App. In the recent project, I plan to use React Native to transform the live broadcast room. One basic function point involved is to click a button in the live broadcast room, and the input box will pop up to make a quick speech. Google rolled up its sleeves because it didn’t have a library ready to use.

Second, implementation ideas

Before explaining, let’s take a look at the final result:


rn-chatui.png

1. Emoji

Since the App has its own expression system, wechat chat expression is found here to demonstrate and establish a public DataSource

Export const EMOTIONS_DATA = {'/{weixiao': require('./emotions/weixiao.png'), /* smile */ '/{piezui': Require (". / emotions/piezui. PNG '), pie mouth / * * / '/ {se' : the require (". / emotions/se. PNG '), color / * * /... }; / / symbol - > Chinese export const EMOTIONS_ZHCN = {' / {weixiao ': [smiles]', '/ {piezui' : '[pie mouth]', '/ {se' : '[color]',... }; // Export const invertKeyValues = obj => object.keys (obj).reduce((acc, key) => {acc[obj[key]] = key; return acc; }, {});Copy the code

EMOTIONS_DATA: Mapping of variables to image resources

EMOTIONS_ZHCN: mapping between variables and Chinese semantics. React Native TextInput does not support inserting images

InvertKeyValues: Invert the EMOTIONS_ZHCN key. Invert the EMOTIONS_ZHCN key. InvertKeyValues: Invert the EMOTIONS_ZHCN key

2. Input box area

The layout of the input field is pretty simple, flexbox, ChatInputBar,


  render() {
    if (Platform.OS === 'android'){
      return this._renderAndroidView();
    }

    return this._renderIosView();

  }

Copy the code

You can see that the Render method ADAPTS to render different views depending on the platform. Notice that the requirement is to click a button to pop up an input box, which is different from the INPUT box fixed at the bottom of IM chat interface, so it naturally comes up with a popover Dialog, while React Native officially provides a component called Modal. This component can be used to overwrite the Native view that contains the React Native root view. However, in the actual development process, there was a pit. That is, if you use Native Modal on The Android system, and the ViewPagerAndroid component is wrapped in it, after the phone shuts down the screen and lights up again, ViewPagerAndroid is unable to slide, the exact reason is not clear, I will continue to study, as the solution is to use a third party library react-native modalbox, take a look at _renderAndroidView() code

_renderAndroidView(){ return <ModalBox swipeToClose={false} backdropOpacity={0} backButtonClose={true} onClosed={() => this._onModalBoxClosed()} style={[styles.container]} position={'bottom'} ref={'modal'}> <TouchableWithoutFeedback onPress={() => this.closeInputBar()}> <View style={styles.box_container}/> </TouchableWithoutFeedback> <View style={styles.inputContainer}> <View style={styles.textContainer}> <TouchableWithoutFeedback onPress={() => this._toogleShowEmojiView()}> <Image style={styles.emojiStyle} source={require('./emotions/ic_emoji.png')}/> </TouchableWithoutFeedback> <TextInput ref="textInput" style={styles.inputStyle} underlineColorAndroid="transparent" Multiline ={true} autoFocus={true} editable={true} placeholderTextColor={'#bababf'} onSelectionChange={(event) => this._onSelectionChange(event)} onChangeText={(text) => this._onInputChangeText(text)} onFocus={() => this._onFocus()} defaultValue={this.state.inputValue}/> <TouchableWithoutFeedback onPress={() => this._onSendMsg()}> <View ref="sendBtnWrapper" style={[styles.sendBtnStyle]}> <Text ref="sendBtnText" Style = {} [styles. SendBtnTextStyle] > send < / Text > < View > < / TouchableWithoutFeedback > < View > < Line lineColor = {} '# bababf' / > { this.state.isEmotionsVisible && <EmotionsView onSelected={(code) => this._onEmojiSelected(code)}/> } </View> </ModalBox>; }Copy the code

In React Native, TextInput does not allow you to insert images, but only plain text. Since you can’t insert emojis, you have to use emojis instead; In addition, TextInput does not have a direct method to retrieve the location of the current editing text cursor After exploration, onSelectionChange can be used to obtain the position of the cursor when clicked for the first time. Later, if text is inserted through input method or expression, it is necessary to maintain the cursor index. The following is the explanation of the reference document

onSelectionChange function

This function is called when the selection range changes and returns arguments in the format {nativeEvent: {selection: {start, end}}}

It is saved in code by the cursorIndex status variable


  _onSelectionChange(event){

    this.setState({
      cursorIndex:event.nativeEvent.selection.start,
    });
  }
  
Copy the code

The code in _renderIosView() and _renderAndroidView() is pretty much the same, just using native Modal,

_renderIosView(){ return <Modal animationType={'slide'} transparent={true} visible={this.state.modalVisible}> ...... <KeyboardAvoidingView behavior={'position'}> ...... <TextInput ref="textInput" style={styles.inputStyle} underlineColorAndroid="transparent" multiline = {true} autoFocus={true} editable={true} keyboardType={'default'} selectionColor={'#56b2f0'} returnKeyType={'send'} Placeholder = {} 'say something' enablesReturnKeyAutomatically = {true} placeholderTextColor = {} '# bababf' onSelectionChange = {(event) = >  this._onSelectionChange(event)} onChangeText={(text) => this._onInputChangeText(text)} onFocus={() => this._onFocus()} defaultValue={this.state.inputValue}/> ...... </KeyboardAvoidingView> </Modal>; }Copy the code

Also different is that KeyboardAvoidingView is wrapped around _renderIosView(), which is used to address a common awkwardness: the pop-up keyboard on your phone often blocks the current view. Renderandroidview () is not wrapped in this component because it automatically adjusts position or the padding at the bottom, depending on the keyboard position. Say many are tears, because it doesn’t work ah… But if you run ChatUI on Android, and you see that the input method doesn’t block the view of the input box, the reason is that in the ChatUI Andorid project, AndroidManifest registered MainActivity set the android: windowSoftInputMode = “adjustResize” attribute, this property allows the view pop-up automatically adjust according to the soft keyboard, so pay attention to, If your project is Native and the React of the development of Native mixed, React in Native depend on the Activity, you must set up corresponding android: windowSoftInputMode attribute to adaptation related requirements, ha ha ha! 😝

3. Expression selection area

I found an open source library rn-Viewpager on the Internet and briefly scanned the source code. On IOS, it is implemented through ScrollView. On Android, it still packages ViewPagerAndroid components like React Native. The advantage of using a third party library is that it can be quickly adapted to both ends, which is appropriate for the requirements, but the pit is also a big drop! Let’s start with a paragraph from the ViewPagerAndroid documentation:

ViewPagerAndroid

A container that allows page turning left and right between subviews. Each ViewPagerAndroid child container is treated as a separate page and stretched to fill. Note that all subviews must be pure views, not custom composite containers.

Note that all subviews must be pure views, not custom composite containers,

Here is my EmotionsView before modification:


  _renderPagerView(){
   ......
   
    viewItems.push(<EmotionsChildView key={0} />);
    viewItems.push(<EmotionsChildView key={1} />);
    viewItems.push(<EmotionsChildView key={2} />);
    return viewItems;
  }

  render() {
    return (
      <IndicatorViewPager
        style={styles.wrapper}
        indicator={this._renderDotIndicator()}>
        { this._renderPagerView() }
      </IndicatorViewPager>
    );
  }

Copy the code

Revised:


  _renderPagerView(){
   ......
   
    viewItems.push(<View key={0}><EmotionsChildView/></View>);
    viewItems.push(<View key={1}><EmotionsChildView/></View>);
    viewItems.push(<View key={2}><EmotionsChildView/></View>);
    return viewItems;
  }

  render() {
    return (
      <IndicatorViewPager
        style={styles.wrapper}
        indicator={this._renderDotIndicator()}>
        { this._renderPagerView() }
      </IndicatorViewPager>
    );
  }

Copy the code

The EmotionsChildView is a compound table View that I package myself. If the child View in ViewPagerAndroid is not a pure View, but a compound container, it will cause the child View inside cannot measure the correct height!

4, facial expression text and text mixed display

RichTextWrapper encapsulates a rich text component for ChatInputBar and has only one textContent property


    <RichTextWrapper textContent={this.state.chatMsg}/>
    
Copy the code

Internal implementation:

Let emojiReg = new RegExp (' \ \ / \ \ {/ a - zA - Z_ {1} 16 '); / / emoticons componentWillReceiveProps regular expressions (nextProps) {this. State. Views. Length = 0; let textContent = nextProps.textContent; this._matchContentString(textContent); } _matchContentString(textContent){let emojiIndex = textContent.search(emojiReg); let checkIndexArray = []; // If no match is found, If (emojiIndex === -1) {this.state.views.push (<Text key) ={'emptyTextView'+(Math.random()*100)}>{textContent}</Text>); } else { if (emojiIndex ! == -1) { checkIndexArray.push(emojiIndex); } // let minIndex = math.min (... checkIndexArray); This.state.views. push(<Text key ={'firstTextView'+(math.random ()*100)}>{textContent.subString (0, minIndex)}</Text>); This._matchemojistring (textContent.subString (minIndex)); } } _matchEmojiString(emojiStr) { let castStr = emojiStr.match(emojiReg); let emojiLength = castStr[0].length; this.state.Views.push(<Image key={emojiStr} style={styles.subEmojiStyle resizeMethod={'auto'3} source={EMOTIONS_DATA[castStr]}/>); this._matchContentString(emojiStr.substring(emojiLength)); }Copy the code

This.state. Views is an array of Views, defined in constructor(), used to hold truncated child Views; ComponentWillReceiveProps textContent () receiving external passed the string matching in specific _matchContentString matching () : If there is no index, return -1. Push a Text subview into the Views array. If there is an emoticon, select the smallest index in checkIndexArray and split the string into three parts: 0-index, index, and index-end. 0-index returns plain text, and index is the emoticon. This is converted into an Image subview and pushed into the Views array. The index-end part is recursed again until the final index is -1 and plain text is returned.

Finally, the source code, there is a need for children to download ~

GitHub:github.com/WangGanxin/…

The resources

  • ReactNative to achieve emoji text and text mix scheme
  • React Native custom View parsing emojis