身为一个web开发者,从来没想到自己会被一个小小的文本输入框折腾成这样。
最近在使用TextInput组件过程中,遇到的一些问题,在此做个分享和记录。
1.键盘遮挡
在web开发时,我们不需要考虑键盘的遮挡,因为系统会自动处理,但是在native这方面需要手动处理,还好官方已经出品了原生解决方案:KeyboardAvodingView
。
android可以通过设置
android:windowSoftInputMode="adjustPan"
系统自动处理键盘遮挡,这种情况下不需要使用该组件
先上代码
<KeyboardAvoidingView
style={{flex:1}}
behavior="padding"
keyboardVerticalOffset={64}
>
<ScrollView style={{flex:1}}>
{/*.....*/}
</ScrollView>
</KeyboardAvoidingView>
看下文档便知,比较有用的就是这两个属性了
keyboardVerticalOffset
behavior
keyboardVerticalOffset
有些情况下你的视图可能距离屏幕顶部有一段距离,这个时候需要用这个进行修正。
网上教程很少详细说这个属性,但是这个属性非常重要。
先让我们看下KeyboardAvoidingView
源码的实现
relativeKeyboardHeight(keyboardFrame: ScreenRect): number {
const frame = this.frame;
if (!frame || !keyboardFrame) {
return 0;
}
const keyboardY = keyboardFrame.screenY - this.props.keyboardVerticalOffset;
// Calculate the displacement needed for the view such that it
// no longer overlaps with the keyboard
return Math.max(frame.y + frame.height - keyboardY, 0);
},
onKeyboardChange(event: ?KeyboardChangeEvent) {
if (!event) {
this.setState({bottom: 0});
return;
}
const {duration, easing, endCoordinates} = event;
const height = this.relativeKeyboardHeight(endCoordinates);
if (duration && easing) {
LayoutAnimation.configureNext({
duration: duration,
update: {
duration: duration,
type: LayoutAnimation.Types[easing] || 'keyboard',
},
});
}
this.setState({bottom: height});
},
可以看到该组件其实是在键盘打开关闭状态改变的时候,更新了botttom
这个状态,而这个值是通过坐标计算而来的。
this.frame
是组件的根节点View onLayout
回调得到值。
这里先用虚拟键盘在屏幕的y坐标减去了传入的keyboardVerticalOffset
获取到键盘的y轴偏移量,然后frame.y + frame.height
计算出容器距离父元素顶部的距离,减去键盘的y轴偏移量,就得出了容器需要偏移的距离。
为什么要减去一个偏移量呢?打个比方,我们的页面都会有状态栏,会有头部(例如使用react-navigation
),这个时候我们在使用KeyboardAvoidingView
的时候,可能并没有从屏幕顶部进行包裹,因为onLayout
得到的y坐标
是相对于父元素的,所以就会造成键盘的screenY
和frame.y
没有以一个起点作为参考,所以需要手动修正这一块距离。
其实把计算改成:
return Math.max(frame.y + this.props.keyboardVerticalOffset + frame.height - keyboardFrame.screenY, 0);
这样就比较好理解了。
behavior
顾名思义就是以何种方式处理,看源码得知主要做了如下处理
- height
键盘打开时把容器的高度减去了上面计算出来的bottom
的值,关闭时又恢复原来大小 - padding
键盘打开时给容器设置了paddingBottom: bottom
- position
键盘打开时给容器设置了bottom:bottom
,也就是让容器向上偏移bottom
大小距离
到底使用哪一个呢,个人觉得还是看使用场景吧。
问题
如果我们的输入框在一个ScrollView
里面,像我们上面写的那个demo一样,而KeyboardAvoidingView
在底部加入了paddingBottom
,仅仅是压缩了ScrollView
的高度,这样本来位置靠下的输入框能显示吗?
结果是可以。
如图所示,ScrollView
自动就滚到输入框的位置了。
这也是最令人困惑的地方。同事说可能是react-native
内部做了什么处理。希望研究过的同学解答下。
2.android设置textAlign引发的bug
BUG-1
如果同时设置了textAlign
和height属性
,那么只要手指在TextInput
上横向滑动几下,placeholder
就不见了,光标也不见了,键盘却能弹出,也能正常输入,但是不显示,用手指再去触摸一下,文字就出来了。。。
ios无此问题。
解决办法:对android不设置高度。
BUG-2
https://github.com/facebook/react-native/issues/12167
https://github.com/facebook/react-native/issues/15764
https://github.com/facebook/react-native/issues/17135
当对TextInput
组件设置textAlign
为right
或者center
时,当手指触摸到TextInput
上方时,外层的ScrollView
将中断滚动,而且TextInput
会获取到焦点,键盘弹出。当textAlign
为left
时则不会有此问题。这个问题仅在android手机上会出现。
提出这个问题的人不少,但是没有找到官方的解决方案。
有人指出使用multiline
和 keyboardType: default
可以解决,尝试了下确实可以,但是实际上我们可能不需要multiline
,而且keyboardType
为其他类型时,placeholder
的文字就会显示了,简直是个迷。。。
还好有人在提出了一个hack写法。
focusInput = () => {
const { textInput } = this;
if (!textInput) return;
if (textInput.isFocused()) {
textInput.blur();
setTimeout(() => textInput.focus(), 500);
} else {
textInput.focus();
}
}
...
<View style={styles.textBox}>
<TextInput
style={styles.textInput}
placeholder={placeholder}
underlineColorAndroid="transparent"
autoCorrect={false}
spellCheck={false}
placeholderTextColor="#a8afc3"
selectionColor="#0064ff"
ref={this.inputRef}
{...rest}
/>
<TouchableOpacity
style={StyleSheet.absoluteFill}
onPress={this.focusInput}
/>
</View>
原理就是屏蔽了textinput
本身的手势,在上面覆盖一个手势遮罩,自己处理textInput的获取焦点和失去焦点。经过尝试,这种方案感觉是可行的,目前还没发有发现明显的问题。
======= 6月20日更新 ========
最新发现了一个问题,由于textinput
被覆盖了,导致无法进行长按复制粘贴了=。=||。
于是修改了一些逻辑,即在获取焦点后,让覆盖层消失。