React Native遮挡输入框问题

字数 334阅读 2461

最近在搞React Native输入框,发现键盘弹起的时候输入框并未随着一起弹起,文档中也并没有明显的说明,作为一个iOS开发,当然是要试试用Native的方式来解决问题,React Native文档中原生模块中有介绍Native向JavaScript发送事件,所以,可以用Native来监听键盘弹出收起事件,然后发送给JavaScript。

#import "RCTBridge.h"
#import "RCTEventDispatcher.h"
@implementation CalendarManager
@synthesize bridge = _bridge;
- (void)calendarEventReminderReceived:(NSNotification *)notification
{
  NSString *eventName = notification.userInfo[@"name"];
  [self.bridge.eventDispatcher sendAppEventWithName:@"EventReminder"
                                               body:@{@"name": eventName}];
}
@end

按照文档的说明,敲出代码之后发现sendAppEventWithName这个方法已经废弃了:


RCTEventDispatcher源码

根据提示信息使用RCTEventEmitter代替,官网并未更新这个类的使用,但是根据头文件提示:"RCTEventEmitter is an abstract base class to be used for modules that emit events to be observed by JS.",应该使用RCTEventEmitter子类来实现消息传递:

#import "CLRNEventEmitter.h"

#define CL_KEYBOARD_SHOW_EVENT @"keyboardWillShow"
#define CL_KEYBOARD_HIDE_EVENT @"keyboardWillHide"

@implementation CLRNEventEmitter

RCT_EXPORT_MODULE();

- (NSArray<NSString *> *)supportedEvents
{
    return @[
             CL_KEYBOARD_SHOW_EVENT,
             CL_KEYBOARD_HIDE_EVENT
             ];
}

- (instancetype)init
{
    if (self = [super init]) {
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(keyboardWillShow:)
                                                     name:UIKeyboardWillShowNotification
                                                   object:nil];
        
        [[NSNotificationCenter defaultCenter] addObserver:self
                                                 selector:@selector(keyboardWillHide:)
                                                     name:UIKeyboardWillHideNotification
                                                   object:nil];
    }
    return self;
}

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

#pragma mark - Keyboard Event
- (void)keyboardWillShow:(NSNotification *)notification
{
    [self sendEvent:CL_KEYBOARD_SHOW_EVENT object:notification.userInfo];
}

- (void)keyboardWillHide:(NSNotification *)notification
{
    [self sendEvent:CL_KEYBOARD_HIDE_EVENT object:notification.userInfo];
}

#pragma mark -

#pragma mark - Base Function
- (void)sendEvent:(NSString *)eventName object:(id)object
{
    [self sendEventWithName:eventName body:object];
}

@end

对应的js文件里面添加监听:

    var keyboardEvent = new NativeEventEmitter(NativeModules.CLRNEventEmitter);

    componentDidMount() {
        this.keyboardWillShow = keyboardEvent.addListener('keyboardWillShow', this._keyboardWillShow.bind(this));
        this.keyboardWillHide = keyboardEvent.addListener('keyboardWillHide', this._keyboardWillHide.bind(this));
    }

    componentWillUnmount() {
        this.keyboardWillShow.remove();
        this.keyboardWillHide.remove();
        dismissKeyboard();
    }

    _keyboardWillShow(infoData) {

        if (!infoData || !infoData.endCoordinates || !infoData.duration) return;

        Animated.timing(this.state.loacationOffInputView, {
            toValue: infoData.endCoordinates.height, // 目标值
            duration: infoData.duration, // 动画时间
            easing: Easing.keyboard // 缓动函数
        }).start();
    }

    _keyboardWillHide(infoData) {

        if (!infoData || !infoData.endCoordinates || !infoData.duration) return;

        Animated.timing(this.state.loacationOffInputView, {
            toValue: 0, // 目标值
            duration: infoData.duration, // 动画时间
            easing: Easing.keyboard, // 缓动函数
        }).start();
    }

以上是通过Native注册通知监听键盘弹出及消失,然后发送消息给JavaScript,JavaScript通过监听做对应的处理。后来咨询了一下js开发的同事,他给我提供了另一个使用js插件的方式解决:react-native-keyboard-spacer,安装只需一行命令,然后在对应的控件里面添加一行代码搞定:

npm install --save react-native-keyboard-spacer
//react-native-keyboard-spacer
import KeyboardSpacer from 'react-native-keyboard-spacer';
import React, { Component } from 'react';
import {
  AppRegistry,
  StyleSheet,
  Image,
  View,
  TextInput
} from 'react-native';

class DemoApp extends Component {
  render() {
    return (
      <View style={[{flex: 1}]}>
        {/* Some random image to show scaling */}
        <Image source={{uri: 'http://img11.deviantart.net/072b/i/2011/206/7/0/the_ocean_cherry_tree_by_tomcadogan-d41nzsz.png', static: true}}
                 style={{flex: 1}}/>

        {/* The text input to put on top of the keyboard */}
        <TextInput style={{left: 0, right: 0, height: 45}}
              placeholder={'Enter your text!'}/>

        {/* The view that will animate to match the keyboards height */}
        <KeyboardSpacer/>
      </View>
    );
  }
}

AppRegistry.registerComponent('DemoApp', () => DemoApp);
//react-native-keyboard-spacer源码:
/**
 * Created by andrewhurst on 10/5/15.
 */
import React, { Component, PropTypes } from 'react';
import {
  Keyboard,
  LayoutAnimation,
  View,
  Dimensions,
  Platform,
  StyleSheet
} from 'react-native';

const styles = StyleSheet.create({
  container: {
    left: 0,
    right: 0,
    bottom: 0,
  },
});

// From: https://medium.com/man-moon/writing-modern-react-native-ui-e317ff956f02
const defaultAnimation = {
  duration: 500,
  create: {
    duration: 300,
    type: LayoutAnimation.Types.easeInEaseOut,
    property: LayoutAnimation.Properties.opacity
  },
  update: {
    type: LayoutAnimation.Types.spring,
    springDamping: 200
  }
};

export default class KeyboardSpacer extends Component {
  static propTypes = {
    topSpacing: PropTypes.number,
    onToggle: PropTypes.func,
    style: View.propTypes.style,
  };

  static defaultProps = {
    topSpacing: 0,
    onToggle: () => null,
  };

  constructor(props, context) {
    super(props, context);
    this.state = {
      keyboardSpace: 0,
      isKeyboardOpened: false
    };
    this._listeners = null;
    this.updateKeyboardSpace = this.updateKeyboardSpace.bind(this);
    this.resetKeyboardSpace = this.resetKeyboardSpace.bind(this);
  }

  componentDidMount() {
    const updateListener = Platform.OS === 'android' ? 'keyboardDidShow' : 'keyboardWillShow';
    const resetListener = Platform.OS === 'android' ? 'keyboardDidHide' : 'keyboardWillHide';
    this._listeners = [
      Keyboard.addListener(updateListener, this.updateKeyboardSpace),
      Keyboard.addListener(resetListener, this.resetKeyboardSpace)
    ];
  }

  componentWillUnmount() {
    this._listeners.forEach(listener => listener.remove());
  }

  updateKeyboardSpace(event) {
    if (!event.endCoordinates) {
      return;
    }

    let animationConfig = defaultAnimation;
    if (Platform.OS === 'ios') {
      animationConfig = LayoutAnimation.create(
        event.duration,
        LayoutAnimation.Types[event.easing],
        LayoutAnimation.Properties.opacity,
      );
    }
    LayoutAnimation.configureNext(animationConfig);

    // get updated on rotation
    const screenHeight = Dimensions.get('window').height;
    // when external physical keyboard is connected
    // event.endCoordinates.height still equals virtual keyboard height
    // however only the keyboard toolbar is showing if there should be one
    const keyboardSpace = (screenHeight - event.endCoordinates.screenY) + this.props.topSpacing;
    this.setState({
      keyboardSpace,
      isKeyboardOpened: true
    }, this.props.onToggle(true, keyboardSpace));
  }

  resetKeyboardSpace(event) {
    let animationConfig = defaultAnimation;
    if (Platform.OS === 'ios') {
      animationConfig = LayoutAnimation.create(
        event.duration,
        LayoutAnimation.Types[event.easing],
        LayoutAnimation.Properties.opacity,
      );
    }
    LayoutAnimation.configureNext(animationConfig);

    this.setState({
      keyboardSpace: 0,
      isKeyboardOpened: false
    }, this.props.onToggle(false, 0));
  }

  render() {
    return (
      <View style={[styles.container, { height: this.state.keyboardSpace }, this.props.style]} />);
  }
}

推荐阅读更多精彩内容