React Native —— TextInput 组件与键盘遮挡问题记录

综述

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

我个人写的一个库:功能区域输入库 react-native-FuncInpu

鉴于之前开发过一个处理键盘高度与自适应的组件库,这里对若干个问题作一个记录。记录的内容包括以下几块:

  • 安卓与 ios 实现键盘弹出的区别
  • 键盘遮挡解决的思路
  • textInput props的坑
  • 理想的生命周期

安卓与 iOS实现键盘弹出的区别

iOS的情况

熟悉iOS开发的同学一定很清楚,在iOS中键盘的遮挡问题就是自己处理的。需要代码中去监听键盘事件的notification,然后自己处理对应的可能被遮挡控件的frame的变化。

UIKIT_EXTERN NSNotificationName const UIKeyboardWillShowNotification __TVOS_PROHIBITED;
UIKIT_EXTERN NSNotificationName const UIKeyboardDidShowNotification __TVOS_PROHIBITED;
UIKIT_EXTERN NSNotificationName const UIKeyboardWillHideNotification __TVOS_PROHIBITED;
UIKIT_EXTERN NSNotificationName const UIKeyboardDidHideNotification __TVOS_PROHIBITED;
UIKIT_EXTERN NSNotificationName const UIKeyboardWillChangeFrameNotification  NS_AVAILABLE_IOS(5_0) __TVOS_PROHIBITED;
UIKIT_EXTERN NSNotificationName const UIKeyboardDidChangeFrameNotification   NS_AVAILABLE_IOS(5_0) __TVOS_PROHIBITED;

另外对于iOS而言,第三方键盘存在着较为特别的情况。从iOS 8开始苹果支持第三方键盘输入,但是市场还会出现键盘无法弹出的问题。

本人使用的是搜狗的第三方键盘,从前经常出现无法弹出,必须要去 设置 -> 通用 -> 键盘 -> 搜狗键盘 -> 必须将允许完全访问关闭再开启,才能重新启用。但是这个问题应该现在是不再会存在了。

从实现的效果来说,第三方键盘比起原生键盘,会多出类似于收起按键等等的功能(原生键盘没有)。

32F3C45F7E8DEA06CE7FD57F905EE870.png

而且第三方键盘实质上是会先调用原生键盘,使得上文提到的notification生效,然后再让自己的键盘生效的。以我调试时候遇到的情况为例,回调会收到键盘的占位高度,如果使用第三方键盘,高度会收到两次。以上图中的搜狗键盘为例,会收到242282两个高度,最后固定的高度是282

安卓的情况

安卓的键盘弹出不存在 ios 的特殊情况。因为键盘的部分是独立于我们页面之外的。也就是说他会自己处理原先被遮挡部分的向上滚动。这里就不作赘述。


键盘遮挡解决的思路

通常的解决思路是,将所有的 TextInput组件的实例都包裹在一个scrollView中。当键盘起来的时候,整体的ScrollView做一个相应的滚动偏移。
文首中介绍的那个第三方库 react-native-keyboard-aware-scroll-view 就是借鉴了这一点来实现.

但是有一点是不可避免的,就是使用了这套方案,从视觉上,一定是键盘先弹出,然后所有的内容页面才会想上滚动。这和原生页面情况下键盘的弹出和页面的上移几乎是同时发生的效果无法比拟。

对比图.gif

这里闪动的有一点快,仔细看是可以看出二者的展示区别的。

产生这种情况的原因是因为rn必须要先从iOS原生获取到键盘的时间之后,再去进行相关的响应。这其中的生命周期及调用顺序,会在最后一个部分 生命周期 中讲到。


TextInput props的坑

针对键盘输入和遮挡的问题,主要是使用react native官方提供的TextInput组件。但是其中有若干属性会影响到键盘遮挡输入内容。下面一一来列举。

  • underlineColorAndroid 仅安卓有效。默认是 true,也就是在安卓环境下,输入框下面默认会加一根有颜色的线,需要将它设成 false 才会消失。

  • onContentSizeChange 在输入框换行的时候被调用到的回调属性。可以在 { nativeEvent: { contentSize: { width, height } } } 中拿到变化后的高度。这个属性也只在multiLinetrue的时候有效。

    需要注意的是,如果我们打断点会发现,在页面初始化渲染,也就是在TextInput初始化的时候,onContentSizeChange 其实是会先被调用一次的。这时候返回的高度,是根据我们设定的 TextInput 中的显示字体的大小来决定的。

  • onFocus 这个属性顾名思义,就是点击输入框时候就会进入的回调。但是这里有一个小坑就是点击输入框后其实会进入好几个回调,因为输入框聚焦和键盘弹出的时间是同时发生的。而且是异步执行的,没有固定的谁先谁后,这时候就需要在生命周期上做处理了。下面最后一个部分会进入探讨。

  • onSubmitEditing 只在单行模式下生效(下面讲 multiline会细说)。这时候需要把returnKey设成send之类的属性,当我们点击发送的时候,会进入到这个回调监听操作。

  • blurOnSubmit —— 如果为true,文本框会在提交的时候失焦。对于单行输入框默认值为true,多行则为false。注意:对于多行输入框来说,如果将blurOnSubmit设为true,则在按下回车键时就会失去焦点同时触发onSubmitEditing事件,而不会换行。
    这条有点绕,不太好理解,我先把文档中的原文摘录下来:

    If true, the text field will blur when submitted. The default value is true for single-line fields and false for multiline fields. Note that for multiline fields, setting blurOnSubmit to true means that pressing return will blur the field and trigger the onSubmitEditing event instead of inserting a newline into the field.

    可以理解为:如果我将blurOnSubmit 设为 true,那么可以在多行情况下使用虚拟键盘上的发送按钮。并传入点击事件方法给他,但是这样一来,光标会失焦,键盘会消失,而且不会换行。这显然不是我想要的。但是不这样做,就无法获取到点击事件。
    目前这个问题无法解决,我在react native的 git issues 上曾提过疑问,但是没有解决的方案。textinput issues

  • multiline
    然后就是这个影响深远的属性,因为他是制约整个 TextInput 与键盘显示之间关系最大的属性。为什么这么说呢?因为换行和不换行导致的代码量不是一个数量级的。

    • 单行的情况
      不换行的情况下,不存在切换滚动等等一系列复杂的操作。而且你可以使用上面提到的TextInput的另一个属性 onSubmitEditting来让键盘上的returnKey切换成我们想要的发送或别的按钮并接管他按下时候的回调事件.(另一个属性returnKeyType有提供)
      下面是接管前和接管后的对比
    接管前.png
    接管后.png
  • 多行的情况
    这是最复杂的情况。如上所述,因为换行势必影响输入框的高度,产生了高度偏移之后,你必须要通知外部包裹的scrollView做相应的偏移。每一次换行,TextInput都会进入onContentSizeChange回调,要去做对应的处理。

生命周期

这里要讨论的主要就是针对3个情况下的生命周期及回调函数

  • 键盘弹出
  • 输入框换行
  • 键盘回缩

键盘弹出

正如上文提到的,当我开始点击一个输入框时,其实实现的是有3两件事:光标的聚焦、键盘的弹出、外部scrollView的滚动
但是这三件事各自的回调都是异步发生的,也就是说,他们的回调的时间顺序可能和我们设想和期望的不一样。

我们先来罗列这三个事情发生的时候,他们各自的回调。

  • 光标的聚焦 —— TextInputonFocus
  • 键盘的弹出
    • keyboardWillShow —— 仅iOS有效
    • keyboardDidShow
    • keyboardWillChangeFrame —— 仅iOS有效
    • keyboardDidChangeFrame —— 仅iOS有效
  • 外部scrollView
    • 滚动结束进入回调 onMomentumScrollEnd

输入框换行

onContentSizeChange 回调。这里再强调一次,当TextInput 被初始化渲染的时候,onContentSizeChange 也会执行,回调的高度根据设定的font大小来决定

键盘回缩

键盘回缩有3种情况会触发到:

  • 用户点击了空白的非键盘输入区部分;
  • 点击了键盘上的回缩按钮(ios 原生键盘没有这个按钮)
  • 代码中调用了Keyboard.dismiss()

而且会和上面类似产生两大类回调:

  • 键盘的弹出
    • keyboardWillHide —— 仅iOS有效
    • keyboardDidHide
    • keyboardWillChangeFrame —— 仅iOS有效
    • keyboardDidChangeFrame —— 仅iOS有效
  • 外部scrollView
    • 滚动结束进入回调 onMomentumScrollEnd

这里还有一个小坑,如果我们设置的发送按钮是一个 button,或者是一个TouchOpacity,那么点击发送以后,会先响应点击了空白区域这一设定,然后再执行 buttononPress 回调。我这里的处理方法是,不使用按键性质的组件,直接采用 <View>onTouchStart 回调,规避了这个问题的发生

send.png

另外,在 ios 下调用 Keyboard.dismiss() 且当前输入键盘为第三方键盘时,有时候会消失失败。我做的方法是键盘调用 Keyboard.dismiss() 之后隔50ms 再调用一次。

在频繁的切换弹出和回缩的过程中,如果没有采用标志位去对这些凌乱的回调进行管控,让他们跑在我们想要的顺序中,就会达不到我们预期想要实现的效果。
鉴于如何实现一个理想化的输入框切换不是本篇想要讨论的内容,这里只讨论如果你要按顺序执行你想要执行的时序,该如何避免其中可能导致的坑。

想了解的读者可以参看 我的一个 git 开源库 有文档说明和参考代码。这了不赘述

  • 首先,光标的聚焦和键盘的弹出显而易见是在调用外部滚动之前需要做的事,这一点毋庸置疑。因为只有你知道了键盘的高度之后,才会让外部去做滚动相应的高度。(这里再一次解释了上面那张 gif 对比图的原因)
  • 其次,输入框高度的切换,一定是会搭配setState操作使用的。也就是说,除了我们上面提到的几个必定会进入的回调外,我们还要考虑到 componentDidUpdate 回调。
  • 最后,调用外部滚动相当于修改了scrollViewcontentOffset,换行相当于修改了整个scrollViewcontentSize,而这一切都会调用渲染并且会有一定的生效时间,如果在生效之前我们调用了setState去做渲染,势必会造成最后页面的偏移结果和我们想象中的不一致。

解决上面这几个问题的方法其实说简单也简单。
一个是要采用标志位来避免进入一些重复的回调,(例如 keyboardDismiss我在上头都会执行两次,第一次会进入到willChangeFrame,但是我进去之后设置了一个标志位,第二次再进去,因为这个标志位,我就不会再执行一次后续的代码了)
第二就是在需要做一些调用的时候加上几十毫秒的延时,让另一个回调先执行。

流程图

下面是两张图借用我之前开发过的一个带功能区域的输入组件(效果参考微信聊天页面底下的功能输入区域)中的流程图,可以感受下生命周期对开发过程中的影响。

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

推荐阅读更多精彩内容

  • 1、通过CocoaPods安装项目名称项目信息 AFNetworking网络请求组件 FMDB本地数据库组件 SD...
    X先生_未知数的X阅读 15,937评论 3 118
  • 第一条时时刻刻都离不开空调 而且被老板坑了 说好的今天碰面 结果又是无音信 害得我改变计划 好多事都被搁置 果然老...
    Admire佩佩阅读 176评论 0 0
  • 多图预警!文末有pdf版链接,pdf版阅读效果更佳。 PDF版下载链接:http://pan.baidu.com/...
    妖叶秋阅读 1,344评论 9 22