详解React-Native解决键盘遮挡问题(Keyboard遮挡问题)

本文介绍了React-Native键盘遮挡问题,分享给大家

综述

键盘遮挡问题,应该是 RN
中常见的了,网上有很多参考文章.但是这次开发的页面中涉及到多行输入框的问题.
键盘响应的几种办法

我的实际用例

网赌平台哪个信誉好 1

示意图


在开发中经常遇到需要输入的地方,RN给我们提过的TextInput虽然好用,可惜并没有处理遮挡问题。

第一部分,

介绍几个键盘遮挡相关的库.(但是这几个库我到最后都没有使用.)

RN 原生有提供几个和⌨️键盘相关的库.

很多时候键盘弹出来都会遮挡住编辑框,让人很头疼。

Keyboard module

Keyboardreact-native库中存在的,相当于 ios
原生中的键盘广播.(UIKeyboardWillShowNotification 那些)
摘录一段代码

class Demo extends Component {
    constructor(props) {
        super(props);
        this.keyboardDidShowListener = null;
        this.keyboardDidHideListener = null;
    }
    componentWillMount () {
        this.keyboardWillShowSub = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
        this.keyboardWillHideSub = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
    }
    componentWillUnmount() {
        //卸载键盘弹出事件监听
        if (this.keyboardDidShowListener != null) {
            this.keyboardDidShowListener.remove();
        }
        //卸载键盘隐藏事件监听
        if (this.keyboardDidHideListener != null) {
            this.keyboardDidHideListener.remove();
        }
    }
    _keyboardDidShow (event) {
        let keyboardhHeight = event.endCoordinates.height;// 获取当前键盘高度
        // this._tableview.scrollToIndex({animated:true,index:keyboardhHeight,viewPosition:0});
        // this._tableview.scrollToOffset({animated:true,offset:keyboardhHeight});
    }
    _keyboardDidHide () {
        // alert('Keyboard Hidden');
    }

本来想在js.coach
库里面找一找第三方的插件,看到最好的一个就是React-native-keyboard-spacer了,然而我们还差一个东西,那就是获取键盘的高度。

dismissKeyboard

调用路径
import dismissKeyboard from '../../../node_modules/react-native/Libraries/Utilities/dismissKeyboard';
这个库是专门用来手动控制键盘消失的.
调用很简单,就是用dismissKeyboard()
一般结合TextInput组件时,可以选择在onDrag(用户手动拖曳)之类的时机让键盘自行消失.

这个我也查了半天并没有提供,获取没找到吧。于是只好自己写原生模块去获取键盘的高度了。

KeyboardAvoidingView

这个库我不大清楚,但是找了一些文章,包括我们下面要提到的Keyboard Aware ScrollView库相关的
Stack Overflow也看到有人提到这个系统提供的库.估计还是因为他的强耦合性.

关于原生iOS获取键盘高度我就不多说了,网上一大堆,我直接贴上我的代码,自己根据RN写的原生模块:

react-native-keyboard-spacer

可以参考下下面这段的调用方法.
他的用法,是将这个组件和 textinput组件放在同级下使用.
缺点是无法控制多个输入框情况下的输入.而且必须和textinput
放同级,如果textinput外部又包了一层,就很难使用.

我尝试过写几个TextInput然后在最底下放一个<KeyboardSpacer />,结果是,假设有
A,B,C 三个输入框, 我在 C 中输入,然后切换到 A 输入,这时候我其实只要 A
输入框上移,但结果是 C 也跟着上移了,而且底部会报一个错误,类似于告诉你
your previous settings has been overrided
也就是说之前写过的设置又被后一个覆盖,比较不靠谱.

<View style={[styles.inputView]}>
    <TextInput
        ref={(textInput) => {
            this.textInput = textInput;
        }}
        style={[styles.textInput, {height: this.state.textInputHeight}]}
</View>
{Platform.OS === 'ios' && <KeyboardSpacer/>}

// 
// 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端做相应的变化。

先总结下碰到的若干问题.

我尝试过自己用Keyboard来获取键盘弹出/消失
事件的时机来解决键盘的遮挡问题.比如说

  • 在这个 Flatlist
    scrollview底部再加一个类似于键盘容器一样的东西(高度和键盘大小一样),让父组件在键盘弹出的时候,把它的高度变大,从而顶上来.
  • 或者直接对flatlist使用绝对布局({position:'absolute'}),
    控制bottom,也就是底部约束.
    我参考过ios
    原生控制键盘的方法,也就是用masonry来控制make.bottom.equalTo来改变底部约束.
  • 使用flatlistscrollToIndex,先让当前
    cell滚动到页面最顶部,避免被遮挡.(具体可以参看React Native
    踩坑日记(十) —— 使用 flatlist
    的滚动处理键盘遮挡的问题.

上述的做法最后都失效了,原因有几个:

  • 键盘弹出的时机.
    这其实就是键盘事件的生命周期,和输入框的焦点聚焦(onFocus)的生命周期之间的问题.
    因为 RN 其实最后调用的是原生的键盘事件,所以键盘弹出的时机一定是优于
    RN 的 UI 更新的时机的.
    网赌平台哪个信誉好,我们可以分别在keyboardWillShow和输入框的onFocus两个回调函数中分别打断点,可以发现
    keyboardWillShow或者说键盘相关的回调函数一定是先被调用到.

网赌平台哪个信誉好 2

示意图

正因为 键盘先弹出,后进入onfocus,
我们如果要实现键盘弹出之前,我先去实现页面的滚动,似乎是不可能的(至少我目前还没发现可以)

  • 如果使用上面说的实现高度或约束的变化,都要去setState,也就意味着要刷新
    UI, 此时,如果你的输入框的锁在的高度,小于键盘的高度,就会出现:
    键盘弹出后立即缩回的情况.也就是说,按照了下面的逻辑在走

    • 键盘先探出
    • 调用了 setState
    • 页面被刷新
    • 刷新页面的同时,动到了底部键盘所处的 UI 高度区域
    • 键盘自动回缩

    这也就是为什么我们的逻辑无法实现的原因.

  • 另外, scrollToIndex 在 安卓上的支持不好,底下是会空出空白的部分的.

好了,原生模块封装好了,接下来看js方面,这个也是老话题了,前面的博客都说了,直接贴代码:

最终解决方案.

找了一大圈,最后还是上 GitHub
上搜了一个1.2K star的第三方库:Keyboard Aware ScrollView
详细的使用说明我就不啰嗦了(可以参看文章开头的链接),只是有几点特别要注意的,在这里记录下.

版本的问题
先来看支持的版本说明:

v0.4.0 requires RN>=0.48
v0.2.0 requires RN>=0.32.0.
v0.1.2 requires RN>=0.27.2 but you should use 0.2.0 in order to make it work with multiple scroll views.
v0.0.7 requires react-native>=0.25.0.
Use v0.0.6 for older RN versions.
  • 因为这个库其实蛮早就已经出来了,所以早期的版本还是只支持ListView的.我们
    目前用的比较多的FlatList的支持是针对RN 0.48以后才有
    flatlist的.目前我用不上.
  • 针对多输入框,其实只要用
    scrollview就已经可以了.这个库提供的KeyboardAwareScrollView看上去也是它的子类(其实
    js 中没有类的概念,姑且这么认为.)
    非要使用flatlist
    的话,可以用另外一种方式来实现,(文章最后会贴下代码的片段)
  • 这条是友情提示. 如果你不小心用错了版本(比如你的 RN
    是0.44,你用了0.4.0的 keyboard 库),系统报错不说,还有可能会把你先行的
    node库给更改掉.我就踩过这个坑.经过 N 次的卸载重装 node,
    npm,都无法解决.最后用了下面这个土办法:

    所有的方式如果都失败了,请找一台运行正常的 Mac, 复制这个路径下的
    node_modules 到本机的对应目录.
    /usr/local/lib/node_modules/npm


代码片段

  • <Cell>是自己定义的单个 Cell 的组件
  • this.dataSource是一个JSON Object Array,存放cell DataSource

    /* flat list 相关 */
    generateSingleCell = (item:Object, onSelectFunc:Function) => {
        return (
            <Cell cellType={item.cellType} key = {item.index}
                  title={item.title} cellHeight={item.height} onSelectCell={onSelectFunc} inputLimit={item.inputLimit}
                  contentText={this.state[item.stateName]} parentStateName={item.stateName}
                  targetScreen={item.targetScreen}
            />
        );
    };

    generateTableViewComponents = ()=>{
        let cmpArray = [];
        for (let item of this.dataSource) {
            let onSelectFunc;
            switch (item.cellType) { // 根据类型,取相应的回调函数
                case cellType.inputCell:
                    onSelectFunc = this._onEndEditText;
                    break;
                case cellType.jmpNativeCell:
                    onSelectFunc = this._onPressJumpNativeCell;
                    break;
                case cellType.jmpCell:
                    onSelectFunc = this._onPressJumpCell;
                    break;
                case cellType.datePickerCell:
                    onSelectFunc = this._onPressDatePicher;
                    break;
                default:
                    break;
            }
            let cell = this.generateSingleCell(item,onSelectFunc);
            cmpArray.push(cell);
        }
        console.log('组合成的 cell =' + cmpArray);
        return cmpArray;
    };

    render() {
        let curSelDateCellDate = this.state[this._curSelDateCell]; //取出选中的那个 cell 的 date
        let listComponents = this.generateTableViewComponents();
        return (
            <View style={styles.container}>
                <KeyboardAwareScrollView>
                    {listComponents}
                </KeyboardAwareScrollView>
            </View>
        );
    }
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();//这里我是处理高度的 
  } 

这里已经拿到高度,接下来就好办了,就是加减问题。

You can leave a response, or trackback from your own site.

Leave a Reply

网站地图xml地图