1. 程式人生 > >React-Native解決鍵盤遮擋問題(Keyboard遮擋問題)

React-Native解決鍵盤遮擋問題(Keyboard遮擋問題)

在開發中經常遇到需要輸入的地方,RN給我們提過的TextInput雖然好用,可惜並沒有處理遮擋問題。

很多時候鍵盤彈出來都會遮擋住編輯框,讓人很頭疼。

本來想在庫裡面找一找第三方的外掛,看到最好的一個就是react-native-keyboard-spacer了,然而我們還差一個東西,那就是獲取鍵盤的高度。

這個我也查了半天並沒有提供,獲取沒找到吧。於是只好自己寫原生模組去獲取鍵盤的高度了。

關於原生IOS獲取鍵盤高度我就不多說了,網上一大堆,我直接貼上我的程式碼,自己根據RN寫的原生模組:

//
//  KeyboardHeight.h
//  Jicheng6
//
//  Created by guojicheng on 16/11/7.
//  Copyright © 2016年 Facebook. All rights reserved.
//

#import <UIKit/UIKit.h>
#import "RCTEventEmitter.h"
#import "RCTBridgeModule.h"

@interface KeyboardHeight : RCTEventEmitter<RCTBridgeModule>

-(void)heightChanged:(int)height;

@property (nonatomic, assign)int kbHeight;

@end
//
//  KeyboardHeight.m
//  Jicheng6
//
//  Created by guojicheng on 16/11/7.
//  Copyright © 2016年 Facebook. All rights reserved.
//

#import "KeyboardHeight.h"

@implementation KeyboardHeight

RCT_EXPORT_MODULE();

- (instancetype)init
{
  self = [super init];
  if (self) {
    self.kbHeight = 0;
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(keyboardDidShow:)
                                                 name:UIKeyboardDidShowNotification
                                               object:nil];
  }
  return self;
}

-(void)keyboardDidShow:(NSNotification*) aNotification
{
  //獲取鍵盤的高度
  NSDictionary *userInfo = [aNotification userInfo];
  NSValue *aValue = [userInfo objectForKey:UIKeyboardFrameEndUserInfoKey];
  CGRect keyboardRect = [aValue CGRectValue];
  if (_kbHeight != keyboardRect.size.height){
    _kbHeight = keyboardRect.size.height;
    [self heightChanged:_kbHeight];
  }
}

RCT_REMAP_METHOD(getKBHeight,
                 resolver:(RCTPromiseResolveBlock)resolve
                 rejecter:(RCTPromiseRejectBlock)reject)
{
  resolve([[NSNumber alloc]initWithInt:_kbHeight]);
}

- (NSArray<NSString *> *)supportedEvents
{
  return @[@"heightChanged"];
}

-(void)heightChanged:(int)height
{
  [self sendEventWithName:@"heightChanged" body:[NSNumber numberWithUnsignedInt:height]];
}

@end
這裡其實我前面的部落格也說過,一開始我想的是通過RCT_REMAP_METHOD去獲得高度,可惜在鍵盤第一次彈出的時候,並不是彈出之後的高度,獲取之後依然是0,所以添加了一個監聽函式heightChanged,當記錄的值和改變的值不一致時,呼叫監聽函式,將值傳給JS端。這樣就可以在檢測變化之後JS端做相應的變化。

好了,原生模組封裝好了,接下來看JS方面,這個也是老話題了,前面的部落格都說了,直接貼程式碼:

import React, { Component } from 'react';
import {
    AppRegistry,
    StyleSheet,
    Text,
    View,
    TouchableOpacity,
    Alert,
    TextInput,
    PixelRatio,
    Linking,
    Keyboard,
    NativeEventEmitter,
} from 'react-native';

var Dimensions = require('Dimensions');
var ScreenWidth = Dimensions.get('window').width;
var ScreenHeight = Dimensions.get('window').height;

var kbHeight = require('NativeModules').KeyboardHeight;
const kbHeightEvt = new NativeEventEmitter(kbHeight);

componentWillMount() {
        this.heightChanged = kbHeightEvt.addListener('heightChanged', this._heightChanged.bind(this));
    }
    componentDidMount() {

    }
    componentWillUnmount() {
        this.heightChanged.remove();
    }
    _heightChanged(data){
        // console.log(data);
        this.keyboardHeight = data;
        this.changeMarginTop();//這裡我是處理高度的
    }

這裡已經拿到高度,接下來就好辦了,就是加減問題。

我們需要拿到輸入框在螢幕中的位置,然後和鍵盤的高度做比較,輸入框的位置我們通過onLayout獲取:

onLayoutParent(event){
        if (this.orgLayoutParent == null){//獲取的父元件的位置,因為要用到計算
            this.orgLayoutParent = event.nativeEvent.layout;
        }
        console.log('parent layout: ', event.nativeEvent.layout);
    }
    onLayoutMail(event){//獲取輸入框的位置,這個位置是相對父元件的位置,所以上面需要獲得父元件的
        this.layoutMail = event.nativeEvent.layout;
    }
    onFocusMail(event){
        this.focusName = 'mail';//定義一個標識,可以區分不同輸入框
        this.changeMarginTop();//統一處理高度的函式
    }
    onSubmitMail(){
        drawLayout.setKBMoveY(0);//當輸入完畢時,重置回原來的狀態
    }
    changeMarginTop(){//計算移動的距離
        var layout = null;
        if (this.focusName == 'mail'){
            layout = this.layoutMail;
        }
        if (layout && this.orgLayoutParent.y + layout.y + layout.height > ScreenHeight - this.keyboardHeight){
            drawLayout.setKBMoveY(-(this.orgLayoutParent.y + layout.y + layout.height - ScreenHeight + this.keyboardHeight));
        }else{//不對的置零處理
            drawLayout.setKBMoveY(0);
        }
    }
    render() {
        return (
            <View style={[styles.container, this.props.style ? this.props.style : {}]} onLayout={this.onLayoutParent.bind(this)}>
                <View style={[styles.viewStyle, {marginTop: 10}]} onLayout={this.onLayoutMail.bind(this)}>//這裡獲取的是相對位置哦
                    <TextInput style={styles.textInputStyle}
                        onChangeText={this.onTextChange.bind(this)}
                        value={this.state.emailPath}
                        placeholder={'請輸入郵箱'}
                        onFocus={this.onFocusMail.bind(this)}//當獲取到焦點時觸發
                        onSubmitEditing={this.onSubmitMail.bind(this)}/>//點選回車時的呼叫,這裡可以根據需求去做
                    <TouchableOpacity onPress={this.onSubmitSend.bind(this)}>
                        <View style={[styles.sendButtonView, {}]}>
                            <Text style={styles.sendButtonText}>
                                傳送
                            </Text>
                        </View>
                    </TouchableOpacity>
                </View>
            </View>
        );
    }


如果你是當前一個元件一個頁面,就沒必要像我這樣做了,加了一個global,去記錄它們的祖父元件(主要是整個頁面向上移動)

距離我們也都算好了,接下來就是給drawLayout加一個動畫,然後動起來不要那麼突兀。

import React, { Component } from 'react';
import {
  StyleSheet,
  Text,
  View,
  TouchableOpacity,
  Animated,
} from 'react-native';

import SendEmail from './SendEmail';

export default class DrawLayout extends Component {
  constructor(props){
    super(props);
    this.state={
      kbShowY: new Animated.Value(0),//設定動畫的初始值
    };
    global.drawLayout = this;//這裡將自己儲存到global裡面,方便它的子元件呼叫
  }
  setKBMoveY(y){
    Animated.timing(//這裡用的是timing均勻變化,具體的引數,可以參考RN的文件,寫的很詳細了,這裡就不囉嗦了。
      this.state.kbShowY,{
        toValue: y,//變化到目的位置
        delay: 250,//延時250毫秒
      },
    ).start();//開始
  }
  componentWillUnmount() {
    global.drawLayout = null;//降這個值賦值為空
  }
  
  render() {
    return (
      <Animated.View style={[styles.container, {marginTop: this.state.kbShowY}]} >//使用Animated.View
        <SendEmail style={{marginTop: 10}}/>
      </Animated.View>
    );
  }
}

這就大功告成了。接著截圖看看效果,雖然有動畫,沒法弄動態圖

最新的發現,可以看我這裡: 點選開啟連結