如何在React Native应用程序中使用反应导航关闭键盘

在移动应用程序中,显示和关闭键盘似乎是一件微不足道的事情,但是当它与react-navigation和模式呈现一起使用时,在自动关闭它方面会很棘手。 至少那是根据我最初的假设。 本文旨在详细介绍我从键盘处理中学到的知识,以及在处理TextInput时如何避免多余的敲击。由于所有库都是开源的,因此还将有很多代码拼写。 在撰写本文时,我正在使用的React Native版本是0.57.5

内置的TextInput组件

React Native带有许多基本组件,其中一个是TextInput,用于通过键盘将文本输入到应用中。

 从'react'导入React,{组件}; 
从'react-native'导入{AppRegistry,TextInput};
 导出默认类UselessTextInput扩展Component { 
构造函数(道具){
超级(道具);
this.state = {text:'Useless Placeholder'};
}
  render(){ 
返回(
<TextInput
样式= {{高度:40,borderColor:'灰色',borderWidth:1}}
onChangeText = {(文本)=> this.setState({text})}
值= {this.state.text}
/>
);
}
}

就是这样,每当我们单击文本输入时,就会出现键盘,允许我们输入值。 要通过按屏幕上的任意位置来关闭键盘,简单的解决方法是将TouchableWithoutFeedbackKeyboard一起使用。 这类似于在iOS UIView拥有UITapGestureRecognizer并调用view.endEditing

 import { Keyboard } from 'react-native' 

Keyboard.dismiss()

ScrollView中的TextInput

通常,在React Native(主要是ScrollView的滚动组件中,我们应该有一些文本输入,以便能够处理较长的内容列表并避免使用键盘。 如果TextInputScrollView则关闭键盘的行为会有所不同,并取决于keyboardShouldPersistTaps

确定敲击后键盘何时保持可见状态。

  • 'never' (默认),在键盘向上弹起时在聚焦文本输入之外点击会关闭键盘。 发生这种情况时,孩子们将不会收到水龙头。
  • 'always''always' ,键盘不会自动关闭,并且滚动视图也不会捕获敲击,但是滚动视图的子级可以捕获敲击。
  • 'handled' ,当水龙头被儿童操纵(或被祖先捕捉)时,键盘不会自动关闭。

在大多数情况下, never模式应该是理想的行为,在聚焦文本输入之外的任何地方单击都可以关闭键盘。

在我的应用程序中,有一些文本输入和一个操作按钮。 场景是用户输入一些信息,然后按该按钮注册数据。 在“ never模式下,我们必须按两次按钮,一次是关闭键盘,两次是onPress 。 因此解决方案是使用always模式。 这样, Button总是首先获得按下事件。

  <ScrollView keyboardShouldPersistTaps='always' /> 

ScrollView关心键盘

可以响应本机ScrollView本机RCTScrollView类具有处理关闭模式的代码

  RCT_SET_AND_PRESERVE_OFFSET(setKeyboardDismissMode,keyboardDismissMode,UIScrollViewKeyboardDismissMode) 

它选择的选项是keyboardDismissMode属性的UIScrollViewKeyboardDismissMode

在滚动视图中开始拖动时关闭键盘的方式。

如您所见,可能的模式是onDraginteractive 。 并通过keyboardShouldPersistTaps对本机做出反应以显示自定义点keyboardShouldPersistTaps

case none键盘不会因拖动而消失。

case onDrag开始拖动时,将关闭键盘。

case interactive键盘跟随拖动触摸屏外移动,可以再次向上拉以取消关闭。

模态内的ScrollView

但是,当ScrollViewModal内部时,这不起作用。 所谓Modal就是我在React Native中的Modal组件。 我唯一使用的库是react-navigation ,它也支持打开全屏模式,但是它们以方式让我们在react-navigation声明模态看起来像是堆栈,而且很混乱,所以我宁愿不使用它。 我在react-native使用Modal ,效果很好。

因此,如果在Modal内部的ScrollView具有TextInputkeyboardShouldPersistTaps无法正常工作。 Modal似乎知道父级ScrollView因此我们必须在每个父级ScrollView上声明keyboardShouldPersistTaps='always' 。 在React Native中, FlatListSectionListSectionList使用ScrollView ,因此我们需要了解所有这些ScrollView组件。

摸索反应导航

由于我的应用程序严重依赖于react-navigation ,因此最好对它的组件有深入的了解,以便我们确定问题出在哪里。 我在下面写了一些关于反应导航结构的文章。

在React Native应用中使用react-navigation 3.0
react-navigation可能是我在React Native应用程序中使用的唯一依赖项。 到目前为止,我对此感到满意,然后是3.0版… codeburst.io

像每个传统的移动应用程序一样,我的应用程序由选项卡导航器中的许多堆栈导航器组成。 在iOS中,这意味着UITabbarController许多UINavigationViewController 。 在react-navigation我在createBottomTabNavigator使用createMaterialTopTabNavigator

 从“反应导航”导入{createMaterialTopTabNavigator} 
从'react-navigation-tabs'导入{createBottomTabNavigator,BottomTabBar}

我遇到键盘问题的屏幕是一个堆栈导航器中从第二个屏幕显示的Modal ,因此让我们检查层次结构中每个可能的ScrollView 。 这个过程涉及大量的代码阅读,这就是我爱开源的方式。

首先让我们从createBottomTabNavigator开始,它使用createTabNavigator及其自己的TabNavigationView

  TabNavigationView类扩展了React.PureComponent  
 导出默认的createTabNavigator(TabNavigationView); 

标签导航器在ScreenContainer具有标签栏视图,该标签栏视图用于包含视图。 ScreenContainer来自react-native-screens“该项目旨在将本机导航容器组件公开给React Native”。 以下是标签导航器的工作方式。

  render(){ 
const {navigation,renderScene,lazy} = this.props;
const {routes} = navigation.state;
const {已加载} = this.state
返回(


{routes.map((route,index)=> {
如果(懒惰&&!loaded.includes(index)){
//如果从未浏览过屏幕,请勿渲染
返回null;

const isFocused = navigation.state.index ===索引
返回(
<ResourceSavingScene
键= {route.key}
style = {StyleSheet.absoluteFill}
isVisible = {isFocused}
>
{renderScene({route})}

);
})}

{this._renderTabBar()}

);
}

使用_renderTabBar函数中的_renderTabBar渲染选项卡栏。 查看代码,整个选项卡导航器与ScrollView无关。

因此,可疑列表上仅剩下createMaterialTopTabNavigator。 我在带有swipeEnabled: true的应用程序中使用它。 通过查看导入,顶部标签导航器具有

 从'../views/MaterialTopTabBar'导入MaterialTopTabBar,{类型TabBarOptions,}; 

MaterialTopTabBar已从react-native-tab-view导入

 从'react-native-tab-view'导入{TabBar}; 

ScrollView

   
<Animated.ScrollView
水平的
keyboardShouldPersistTaps =“处理”

该属性keyboardShouldPersistTaps最初设置为always ,然后又设置为handle,以避免键盘打开时无法按下选项卡栏中任何按钮的错误https://github.com/react-native-community/react-native -tab-view /问题/ 375

但是此TabBar与我们的问题无关,因为它仅用于包含标签栏按钮。

在createMaterialTopTabNavigator中刷卡

再来看一下createMaterialTopTabNavigator,我们从react-native-tab-view看到了更多导入

 从'react-native-tab-view'导入{TabView,PagerPan}; 

swipeEnabled传入了swipeEnabled

 返回( 
<TabView
{...休息}
navigationState = {navigation.state}
animationEnabled = {animationEnabled}
swipeEnabled = {swipeEnabled}
onAnimationEnd = {this._handleAnimationEnd}
onIndexChange = {this._handleIndexChange}
onSwipeStart = {this._handleSwipeStart}
renderPager = {renderPager}
renderTabBar = {this._renderTabBar}
renderScene = {
/ * $ FlowFixMe * /
this._renderScene
}
/>
);

并呈现PagerDefault,而后者又使用iOS版PagerScroll

 从'react-native'导入{Platform}; 
 让Pager; 
 开关(Platform.OS){ 
案例“ android”:
Pager = require('./ PagerAndroid')。default;
打破;
案例'ios':
Pager = require('./ PagerScroll')。default;
打破;
默认:
分页器= require('./ PagerPan')。default;
打破;
}
 导出默认的寻呼机; 

因此,PagerScroll使用ScrollView处理滚动以匹配用户可以在页面之间滚动的材质样式,并且它具有keyboardShouldPersistTaps=”always”应该正确。

 返回( 
<ScrollView
水平的
pagesEnabled
directionalLockEnabled
keyboardDismissMode =“拖曳”
keyboardShouldPersistTaps =“总是”

因此,在react-navigation ,没有什么可疑的,这促使我查看项目中的代码。

调试FlatList,SectionList和ScrollView

就像我在本文开头所述,根本问题是我们需要为层次结构中的所有父ScrollView声明keyboardShouldPersistTaps 。 这意味着要注意任何FlatListSectionListScrollView

幸运的是,有react-devtools可以显示react应用程序中所有渲染组件的树,并且在react native的Debugging部分中也有介绍。

您可以使用独立版本的React Developer Tools来调试React组件层次结构。 要使用它,请全局安装react-devtools软件包:

 npm install -g react-devtools 

因此,在搜索之后,我发现层次结构中有一个SectionList,应该具有keyboardShouldPersistTaps='always'而没有。