React Native 踩坑日记(九) —— 键盘遮挡问题

综述

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

我的实际用例


示意图

第一部分,

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

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

Keyboard module

Keyboard -- react-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');
    }

dismissKeyboard

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

KeyboardAvoidingView

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

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/>}

第二部分

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

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

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

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

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

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

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

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

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

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

最终解决方案.

找了一大圈,最后还是上 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>
        );
    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,099评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,828评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,540评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,848评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,971评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,132评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,193评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,934评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,376评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,687评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,846评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,537评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,175评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,887评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,134评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,674评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,741评论 2 351

推荐阅读更多精彩内容