综述
键盘遮挡问题,应该是 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
然后在最底下放一个
,结果是,假设有 A,B,C 三个输入框, 我在 C 中输入,然后切换到 A 输入,这时候我其实只要 A 输入框上移,但结果是 C 也跟着上移了,而且底部会报一个错误,类似于告诉你 your previous settings has been overrided
也就是说之前写过的设置又被后一个覆盖,比较不靠谱.
{
this.textInput = textInput;
}}
style={[styles.textInput, {height: this.state.textInputHeight}]}
{Platform.OS === 'ios' && }
第二部分
先总结下碰到的若干问题.
我尝试过自己用Keyboard
来获取键盘弹出/消失 事件的时机来解决键盘的遮挡问题.比如说
- 在这个
Flatlist
或scrollview
底部再加一个类似于键盘容器一样的东西(高度和键盘大小一样),让父组件在键盘弹出的时候,把它的高度变大,从而顶上来. - 或者直接对
flatlist
使用绝对布局({position:'absolute'}
), 控制bottom
,也就是底部约束.
我参考过ios 原生控制键盘的方法,也就是用masonry
来控制make.bottom.equalTo
来改变底部约束. - 使用
flatlist
的scrollToIndex
,先让当前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
的组件 -
this.dataSource
是一个JSON Object Array
,存放cell DataSource
/* flat list 相关 */
generateSingleCell = (item:Object, onSelectFunc:Function) => {
return (
|
);
};
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 (
{listComponents}
);
}