源码地址:https://github.com/facebook/react-native/blob/master/Libraries/Components/Keyboard/KeyboardAvoidingView.js
1. 组件原理 Keyboardavoidingview 组件通常用于防止键盘遮挡住界面可视区域。其原理也很简单,Keyboardavoidingview 组件本身是一个容器,当键盘弹出后,Keyboardavoidingview 会自动缩减自身容器的高度,从而达到防止键盘遮挡的目的。
2. 源码解析 该组件在 Render 函数中进行了条件渲染用来处理不同的渲染方式:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 render (): React .Node { const { behavior, children, contentContainerStyle, enabled, keyboardVerticalOffset, style, ...props } = this .props ; const bottomHeight = enabled ? this .state .bottom : 0 ; switch (behavior) { case 'height' : let heightStyle; if (this ._frame != null && this .state .bottom > 0 ) { heightStyle = { height : this ._initialFrameHeight - bottomHeight, flex : 0 , }; } return ( <View ref ={this.viewRef} style ={StyleSheet.compose(style, heightStyle )} onLayout ={this._onLayout} // 记录初始的 {...props }> {children} </View > ); case 'position' : return ( <View ref ={this.viewRef} style ={style} onLayout ={this._onLayout} {...props }> <View style ={StyleSheet.compose(contentContainerStyle, { bottom: bottomHeight , })}> {children} </View > </View > ); case 'padding' : return ( <View ref ={this.viewRef} style ={StyleSheet.compose(style, {paddingBottom: bottomHeight })} onLayout ={this._onLayout} {...props }> {children} </View > ); default : return ( <View ref ={this.viewRef} onLayout ={this._onLayout} style ={style} {...props }> {children} </View > ); } }
在进行不同组件的渲染时,其重点就是去根据键盘的高度,实时的去计算容器的高度(或者是边距,又或者时位置偏移量)。
以 height
模式为例,其容器的高度为 this._initialFrameHeight - bottomHeight
。其中 this._initialFrameHeight
指的是初始状态下的容器高度,该值会在容器初次触发 onLayout
事件时被记录:
1 2 3 4 5 6 7 8 9 _onLayout = (event: ViewLayoutEvent ) => { this ._frame = event.nativeEvent .layout ; if (!this ._initialFrameHeight ) { this ._initialFrameHeight = this ._frame .height ; } this ._updateBottomIfNecesarry (); };
而 bottomHeight
就是键盘高度,该值对应组件 state 中的 bottom
,在组件每次触发容器 onLayout
事件以及触发键盘事件 时都会重新计算 bottom
的值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 _updateBottomIfNecesarry = () => { if (this ._keyboardEvent == null ) { this .setState ({bottom : 0 }); return ; } const {duration, easing, endCoordinates} = this ._keyboardEvent ; const height = this ._relativeKeyboardHeight (endCoordinates); if (this .state .bottom === height) { return ; } if (duration && easing) { LayoutAnimation .configureNext ({ duration : duration > 10 ? duration : 10 , update : { duration : duration > 10 ? duration : 10 , type : LayoutAnimation .Types [easing] || 'keyboard' , }, }); } this .setState ({bottom : height}); };
计算键盘的高度时需要去通过判断各个组件的位置才能准确得出。首先,我们在 _updateBottomIfNecesarry
中可以获取到键盘事件,从而得到键盘展开后距离屏幕顶端的距离(keyboardFrame.screenY)。然后我们可以通过获取当前容器距离屏幕顶部的距离(this._frame.y)以及当前容器的高度(this._frame.height),将其相加并于键盘距离屏幕顶部的高度相减,即可得出键盘的高度:
这一操作在 this._relativeKeyboardHeight
实现:
1 2 3 4 5 6 7 8 9 10 11 12 _relativeKeyboardHeight (keyboardFrame): number { const frame = this ._frame ; if (!frame || !keyboardFrame) { return 0 ; } const keyboardY = keyboardFrame.screenY - this .props .keyboardVerticalOffset ; return Math .max (frame.y + frame.height - keyboardY, 0 ); }
需要注意的是,我们在计算 keyboardY
时减去了一个 keyboardVerticalOffset
,该数值可以作为参数传入到 <Keyboardavoidingview />
组件中,那么该数值究竟以为这什么?
在官方文档中解释到:这是用户屏幕顶部和react native视图之间的距离,在某些用例中可能不为零,默认为0。这句话很抽象,让我们具体到一个实例中来讲,我们想象这样一个布局结构:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <AppView > <Header /> <ContentView > <Keyboardavoidingview > <TextInput > <TextInput > <TextInput > <TextInput > </Keyboardavoidingview > </ContentView > </AppView >
在上面的布局中,ContentView 为了保证内部元素的定位相时对于其本身的,因此其定位属性是 相对定位 。此时,Keyboardavoidingview 组件内部计算自己的定位高度时(layout.y),计算的高度是从 ContentView 算起的,那么减去了键盘位置偏移量(keyboardFrame.screenY)后,就会发现减多了。为了避免这种情况,就加入了 keyboardVerticalOffset
属性来手动矫正偏移量: