React-native键盘遮挡输入框问题的解决

  2016年10月25日更新:

  现在有一个更准确一点的做法是用一个View包裹住TextInput,然后通过该View的onLayout方法获取该输入框的y轴位置,再减去一个适当的高度去处理scrollview的滚动,如下所示:

<View onLayout={this._downloadLayout.bind(this)}
column',alignItems:'flex-start'}}>
<TextInput
输入下载地址'
ref = 'downloadInput'
onFocus = {this._downLoadFocus.bind(this)}
onChangeText={(text) => this.setState({downloadUrl:text})}
/>
</View>
然后实现_downloadLayout方法:
_downloadLayout(e){
    this.setState({


downloadY:e.nativeEvent.layout.y,


});


}


之后再实现TextInput的onFocus方法,对包裹的整个scrollview页面进行滚动:


_downLoadFocus(){
let scroller = this.refs.scroller;
iOS&& setTimeout(()=>{
let y = this.state.downloadY - 1/3*Dev_height;//Dev_height为屏幕的高度
scroller&&scroller.scrollTo({x:0, y:y, animated:true});
},50);
}
这样的处理适合大多数的情况。


评论里有小伙伴说React.findNodeHandle已经不可以使用了,应该是使用了rn更新的版本,所以我们在使用的时也需要根据版本的不同去选择合适的方法,


感谢他的提醒,新版本可以使用下面这个方法


import ReactNative from 'react-native';


...


ReactNative.findNodeHandle(...)

RN中要解决键盘遮挡输入框的问题其实有挺多方式,在这里只是记录其中的一些个人实际开发中使用到的。

方式一、使用scrollTo方法,这也是最简单最粗暴的,只是需要计算scrollview滚动的距离,并且处理一些体验的bug问题。大致思路是:组件render方法中使用scrollview,并且设置scrollview的keyboardShouldPersistTaps={true}(此步一定不能少,如果缺少该属性,接下来的一步将会不起作用),然后在scrollview中用一个view作为container包裹所有剩余的子视图,比如Text,TouchableHighlight之类的,并且用onStartShouldSetResponderCapture截取该view的事件,用以解决当点击页面上的按钮时,第一次点击只会收起键盘,第二次点击才会响应按钮方法的bug。然后在TextInput的onFocus方法中滚动scrollview,在onEndEditing中恢复scrollview的滚动。以下是在具体实现中的代码。

render方法的实现:

render:function() {

  return(

    <View style={styles.container}>

    <NavigationBar title={'绑定手机号'} onBackPress={this.onBackPress}/>

    <ScrollView ref='scroll' keyboardShouldPersistTaps={true} >

      <View style={styles.content} onStartShouldSetResponderCapture={(e) => {

        const target = e.nativeEvent.target;

        if (target !== React.findNodeHandle(this.refs.phoneInput) && target !== React.findNodeHandle(this.refs.codeInput)) {

          this.refs.phoneInput.blur();

          this.refs.codeInput.blur();

        }}}>

        <TextInput

          style = {styles.cardNumText}

          ref = 'phoneInput'

          onFocus={this.scrollViewTo.bind(this)}

          onEndEditing={()=>{this.refs.scroll.scrollTo(0)}}

          onChange = {this.cardNumberTextChanged.bind(this)}

          placeholder = '请输入预留手机号'

          placeholderTextColor = '#481A5C'

          keyboardType = 'numeric'

        />

        <View style = {styles.lineView}></View>

          <TouchableHighlight style = {styles.topButton} underlayColor='#9B9B9B' onPress = {this.jumpToNextPage.bind(this)}>

            <Text style = {styles.buttonText}>发送验证码</Text>

          </TouchableHighlight>

        <TextInput

          style = {styles.cardNumText}

          ref = 'codeInput'

          onFocus={this.scrollViewTo.bind(this)}

          onEndEditing={()=>{this.refs.scroll.scrollTo(0)}}

          placeholder = '输入验证码'

          placeholderTextColor = '#999'

          onChange = {this.cardNumberTextChanged}

          keyboardType = 'number-pad'

        />

        <View style = {styles.lineView}></View>

        <Text style = {styles.protectText}>

           XXXXXXXXXXXXXXXXXXX

        </Text>

        <TouchableHighlight style = {styles.downButton} underlayColor='#481A5C' onPress = {this.jumpToNextPage.bind(this)}>

          <Text style = {styles.buttonText}>下一步</Text>

        </TouchableHighlight>

      </View>

    </ScrollView>

  </View>);

}

  onFocus时调用的scrollViewTo方法的实现:

  scrollViewTo:function(e){

    let target = e.nativeEvent.target;

    let scrollLength = 100;

    if (target=== React.findNodeHandle(this.refs.codeInput)) {

      scrollLength = 160;

    }

    this.refs.scroll.scrollTo(scrollLength);

  },

  方式二、使用View包裹时,通过设置View的marginTop属性并且结合动画来实现:初始化一个state对象的值viewMarginTop用于设置Animated.View的marginTop,在textInput的onfocus时改变viewMarginTop的值,在onEndediting时恢复或者设置新的marginTop。具体为首先引入Animated,并且初始化state方法。(state内值的变化会触发界面上相关元素的再次熏染,具有reactivecocoa的相同的作用)

  getInitialState: function () {

    return {

      viewMarginTop: new Animated.Value(0),

    };

  },

在需要上升的视图中使用Animated.View,设置其mairginTop为viewMarginTop

<Animated.View style={{marginTop:this.state.viewMarginTop}}>

    //当然不建议将样式写在这里,这样会导致每次熏染都创建一次样式,你应该将样式定义到StyleSheet中

    //your Views and component

</Animated.View>

然后在onFucos的方法中用动画改变viewMarginTop的值,如下

  Animated.timing(

    this.state.viewMarginTop,

    {

      toValue: 160,

      duration: 250,

    }

  ).start();

要恢复只需要在onEndediting中用同样的原理恢复viewMarginTop的值即可.

方式三、通过监听scrollview上键盘的出现和消失,在出现和消失方法中设置某个state值的变化,来设置scrollview的contentInset,该方法只是在github上看过,具体本人并没有用过即:

1.在页面熏染完时添加监听

componentDidMount: function () {

  // Keyboard events监听

  DeviceEventEmitter.addListener('keyboardWillShow', this.updateKeyboardSpace)

  DeviceEventEmitter.addListener('keyboardWillHide', this.resetKeyboardSpace)

},

componentWillUnmount: function () {

  // TODO: figure out if removeAllListeners is the right thing to do

  DeviceEventEmitter.removeAllListeners('keyboardWillShow')

  DeviceEventEmitter.removeAllListeners('keyboardWillHide')

},

getInitialState: function (props) {//初始化变量

  this.viewIsInsideTabBar = false

  return {

    keyboardSpace: 0,

  }

},

// Keyboard actions

updateKeyboardSpace: function (frames) {

  const keyboardSpace = frames.endCoordinates.height//获取键盘高度

  this.setState({

    keyboardSpace: keyboardSpace,

  })

},

resetKeyboardSpace: function () {

  this.setState({

    keyboardSpace: 0,

  })

},

//设置scrollview的contentInset

<ScrollView

  ref='keyboardView'

  keyboardDismissMode='interactive'

  contentInset={{bottom: this.state.keyboardSpace}}

  showsVerticalScrollIndicator={true}

</ScrollView>