ReactNative中ScrollView和弹出键盘的冲突

最近写的React Native项目中有一个页面是外层ScrollView里面包含一个TextInput输入框,和一个提交按钮,大致结构如下:

1
2
3
4
5
6
7
<ScrollView>
<TextInput>
</TextInput>
<CustomButton>
</CustomButton>
...
</ScrollView>

点击输入框TextInput会弹出软键盘供用户输入内容,输入完成之后点击CustomButton会有后续的提交动作

问题

功能实现很简单,提交测试之后发现被提了一个issue,内容是:

每次输入完内容点击CustomButton提交的时候,都要点击两次才真正提交,点击CustomButton第一次只是会关闭软键盘,第二次才是真正的提交动作,希望能够改为点击一次就完成提交动作

看到这个issue之后,赶紧安装测试了一下,发现真的是每次要提交的时候需要点击两次,一次关闭软键盘,一次提交动作,那既然真有问题,就要解决啊

根据之前多年开发Android的经验,我首先觉得是外层的ScrollView拦截了点击事件,如果软键盘在的话dismiss掉软键盘,同时消费掉了点击事件,不再向下传递。从而导致点击事件在软键盘出现的时候,无法到达CustomButton组件

根据猜想搜索了一下,发现确实有网友遇到这种情况,问题的原因也大致就是我上面的猜想,既然涉及到点击事件传递问题,那么通过组件重写点击事件获取过程中的onStartShouldSetResponderCapture回调是可以达到目的的,这种相对比较复杂,需要对React Native的事件获取-分发-消费有比较清晰和深入的了解,因为这里官方提供了更方便的解决方法,一般情况下就不会选择这种解法了,不过了解ReactNative的事件流程还是有必要的,改天我详细写写ReactNative的事件分化相关的内容

解法

在ScrollView中添加如下属性:

1
2
3
keyboardShouldPersistTaps='handled'
或者
keyboardShouldPersistTaps='always'

如果你的rn版本比较低,那么使用下面的属性:

1
keyboardShouldPersistTaps='true'

这些属性代表什么意思,官方文档说的很清楚,其实无非就是帮我们处理这个点击事件ScrollView拦截还是不拦截,是否允许子组件收到点击事件,文档解释如下:

1
2
3
4
5
6
7
8
keyboardShouldPersistTaps
Determines when the keyboard should stay visible after a tap.

'never': (the default), tapping outside of the focused text input when the keyboard is up dismisses the keyboard. When this happens, children won't receive the tap.
'always': the keyboard will not dismiss automatically, and the scroll view will not catch taps, but children of the scroll view can catch taps.
'handled': the keyboard will not dismiss automatically when the tap was handled by a children, (or captured by an ancestor).
false: deprecated, use 'never' instead
true: deprecated, use 'always' instead