mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-04-21 19:11:22 +08:00
RN: Switch KeyboardAvoidingView to Class Syntax
Summary: Refactors `KeyboardAvoidingView` by using class syntax and fixing all Flow errors. Note that there's still a bunch of sketchy stuff going on in this component with mutated instance variables (that are used in `render`!) and unsafe lifecycle methods. But at least now it's a little bit less painful on the eyes. Reviewed By: TheSavior Differential Revision: D7987443 fbshipit-source-id: f5c27a9dd383c430d9a5a9dc0b6e10e2c4fe8dd9
This commit is contained in:
committed by
Facebook Github Bot
parent
858f7f8331
commit
c017dcb0f2
@@ -25,16 +25,19 @@ type KeyboardEventName =
|
||||
| 'keyboardWillChangeFrame'
|
||||
| 'keyboardDidChangeFrame';
|
||||
|
||||
export type KeyboardEvent = {|
|
||||
+duration?: number,
|
||||
+easing?: string,
|
||||
+endCoordinates: {|
|
||||
+width: number,
|
||||
+height: number,
|
||||
+screenX: number,
|
||||
+screenY: number,
|
||||
|},
|
||||
|};
|
||||
type ScreenRect = $ReadOnly<{|
|
||||
screenX: number,
|
||||
screenY: number,
|
||||
width: number,
|
||||
height: number,
|
||||
|}>;
|
||||
|
||||
export type KeyboardEvent = $ReadOnly<{|
|
||||
duration?: number,
|
||||
easing?: string,
|
||||
endCoordinates: ScreenRect,
|
||||
startCoordinates?: ScreenRect,
|
||||
|}>;
|
||||
|
||||
type KeyboardEventListener = (e: KeyboardEvent) => void;
|
||||
|
||||
|
||||
@@ -10,97 +10,69 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
const createReactClass = require('create-react-class');
|
||||
const Keyboard = require('Keyboard');
|
||||
const LayoutAnimation = require('LayoutAnimation');
|
||||
const Platform = require('Platform');
|
||||
const PropTypes = require('prop-types');
|
||||
const React = require('React');
|
||||
/* $FlowFixMe(>=0.54.0 site=react_native_oss) This comment suppresses an error
|
||||
* found when Flow v0.54 was deployed. To see the error delete this comment and
|
||||
* run Flow. */
|
||||
const TimerMixin = require('react-timer-mixin');
|
||||
const StyleSheet = require('StyleSheet');
|
||||
const View = require('View');
|
||||
const ViewPropTypes = require('ViewPropTypes');
|
||||
|
||||
import type EmitterSubscription from 'EmitterSubscription';
|
||||
import type {ViewStyleProp} from 'StyleSheet';
|
||||
import type {ViewProps, ViewLayout, ViewLayoutEvent} from 'ViewPropTypes';
|
||||
|
||||
type ScreenRect = {
|
||||
screenX: number,
|
||||
screenY: number,
|
||||
width: number,
|
||||
height: number,
|
||||
};
|
||||
type KeyboardChangeEvent = {
|
||||
startCoordinates?: ScreenRect,
|
||||
endCoordinates: ScreenRect,
|
||||
duration?: number,
|
||||
easing?: string,
|
||||
};
|
||||
import type {KeyboardEvent} from 'Keyboard';
|
||||
|
||||
type Props = $ReadOnly<{|
|
||||
...ViewProps,
|
||||
|
||||
/**
|
||||
* Specify how to react to the presence of the keyboard.
|
||||
*/
|
||||
behavior?: ?('height' | 'position' | 'padding'),
|
||||
|
||||
/**
|
||||
* Style of the content container when `behavior` is 'position'.
|
||||
*/
|
||||
contentContainerStyle?: ?ViewStyleProp,
|
||||
enabled?: ?boolean,
|
||||
keyboardVerticalOffset?: ?number,
|
||||
|
||||
/**
|
||||
* Controls whether this `KeyboardAvoidingView` instance should take effect.
|
||||
* This is useful when more than one is on the screen. Defaults to true.
|
||||
*/
|
||||
enabled: ?boolean,
|
||||
|
||||
/**
|
||||
* Distance between the top of the user screen and the React Native view. This
|
||||
* may be non-zero in some cases. Defaults to 0.
|
||||
*/
|
||||
keyboardVerticalOffset: number,
|
||||
|}>;
|
||||
|
||||
type State = {|
|
||||
bottom: number,
|
||||
|};
|
||||
|
||||
const viewRef = 'VIEW';
|
||||
|
||||
/**
|
||||
* This is a component to solve the common problem of views that need to move out of the way of the virtual keyboard.
|
||||
* It can automatically adjust either its height, position or bottom padding based on the position of the keyboard.
|
||||
* View that moves out of the way when the keyboard appears by automatically
|
||||
* adjusting its height, position, or bottom padding.
|
||||
*/
|
||||
const KeyboardAvoidingView = ((createReactClass({
|
||||
displayName: 'KeyboardAvoidingView',
|
||||
mixins: [TimerMixin],
|
||||
class KeyboardAvoidingView extends React.Component<Props, State> {
|
||||
static defaultProps = {
|
||||
enabled: true,
|
||||
keyboardVerticalOffset: 0,
|
||||
};
|
||||
|
||||
propTypes: {
|
||||
...ViewPropTypes,
|
||||
/**
|
||||
* Specify how the `KeyboardAvoidingView` will react to the presence of
|
||||
* the keyboard. It can adjust the height, position or bottom padding of the view
|
||||
*/
|
||||
behavior: PropTypes.oneOf(['height', 'position', 'padding']),
|
||||
_frame: ?ViewLayout = null;
|
||||
_subscriptions: Array<EmitterSubscription> = [];
|
||||
|
||||
/**
|
||||
* The style of the content container(View) when behavior is 'position'.
|
||||
*/
|
||||
contentContainerStyle: ViewPropTypes.style,
|
||||
state = {
|
||||
bottom: 0,
|
||||
};
|
||||
|
||||
/**
|
||||
* This is the distance between the top of the user screen and the react native view,
|
||||
* may be non-zero in some use cases. The default value is 0.
|
||||
*/
|
||||
keyboardVerticalOffset: PropTypes.number.isRequired,
|
||||
/**
|
||||
* This is to allow us to manually control which KAV shuld take effect when
|
||||
* having more than one KAV at the same screen
|
||||
*/
|
||||
enabled: PropTypes.bool.isRequired,
|
||||
},
|
||||
|
||||
getDefaultProps() {
|
||||
return {
|
||||
enabled: true,
|
||||
keyboardVerticalOffset: 0,
|
||||
};
|
||||
},
|
||||
|
||||
getInitialState() {
|
||||
return {
|
||||
bottom: 0,
|
||||
};
|
||||
},
|
||||
|
||||
subscriptions: ([]: Array<EmitterSubscription>),
|
||||
frame: (null: ?ViewLayout),
|
||||
|
||||
_relativeKeyboardHeight(keyboardFrame: ScreenRect): number {
|
||||
const frame = this.frame;
|
||||
_relativeKeyboardHeight(keyboardFrame): number {
|
||||
const frame = this._frame;
|
||||
if (!frame || !keyboardFrame) {
|
||||
return 0;
|
||||
}
|
||||
@@ -110,10 +82,10 @@ const KeyboardAvoidingView = ((createReactClass({
|
||||
// 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) {
|
||||
_onKeyboardChange = (event: ?KeyboardEvent) => {
|
||||
if (event == null) {
|
||||
this.setState({bottom: 0});
|
||||
return;
|
||||
}
|
||||
@@ -135,17 +107,13 @@ const KeyboardAvoidingView = ((createReactClass({
|
||||
});
|
||||
}
|
||||
this.setState({bottom: height});
|
||||
},
|
||||
};
|
||||
|
||||
_onLayout(event: ViewLayoutEvent) {
|
||||
this.frame = event.nativeEvent.layout;
|
||||
},
|
||||
_onLayout = (event: ViewLayoutEvent) => {
|
||||
this._frame = event.nativeEvent.layout;
|
||||
};
|
||||
|
||||
UNSAFE_componentWillUpdate(
|
||||
nextProps: Object,
|
||||
nextState: Object,
|
||||
nextContext?: Object,
|
||||
): void {
|
||||
UNSAFE_componentWillUpdate(nextProps: Props, nextState: State): void {
|
||||
if (
|
||||
nextState.bottom === this.state.bottom &&
|
||||
this.props.behavior === 'height' &&
|
||||
@@ -155,44 +123,55 @@ const KeyboardAvoidingView = ((createReactClass({
|
||||
// triggered by parent component re-rendering, no need for bottom to change.
|
||||
nextState.bottom = 0;
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
UNSAFE_componentWillMount() {
|
||||
componentDidMount(): void {
|
||||
if (Platform.OS === 'ios') {
|
||||
this.subscriptions = [
|
||||
this._subscriptions = [
|
||||
Keyboard.addListener('keyboardWillChangeFrame', this._onKeyboardChange),
|
||||
];
|
||||
} else {
|
||||
this.subscriptions = [
|
||||
this._subscriptions = [
|
||||
Keyboard.addListener('keyboardDidHide', this._onKeyboardChange),
|
||||
Keyboard.addListener('keyboardDidShow', this._onKeyboardChange),
|
||||
];
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subscriptions.forEach(sub => sub.remove());
|
||||
},
|
||||
componentWillUnmount(): void {
|
||||
this._subscriptions.forEach(subscription => {
|
||||
subscription.remove();
|
||||
});
|
||||
}
|
||||
|
||||
render(): React.Element<any> {
|
||||
// $FlowFixMe(>=0.41.0)
|
||||
const {behavior, children, style, ...props} = this.props;
|
||||
const bottomHeight = this.props.enabled ? this.state.bottom : 0;
|
||||
render(): React.Node {
|
||||
const {
|
||||
behavior,
|
||||
children,
|
||||
contentContainerStyle,
|
||||
enabled,
|
||||
keyboardVerticalOffset, // eslint-disable-line no-unused-vars
|
||||
style,
|
||||
...props
|
||||
} = this.props;
|
||||
const bottomHeight = enabled ? this.state.bottom : 0;
|
||||
switch (behavior) {
|
||||
case 'height':
|
||||
let heightStyle;
|
||||
if (this.frame) {
|
||||
if (this._frame != null) {
|
||||
// Note that we only apply a height change when there is keyboard present,
|
||||
// i.e. this.state.bottom is greater than 0. If we remove that condition,
|
||||
// this.frame.height will never go back to its original value.
|
||||
// When height changes, we need to disable flex.
|
||||
heightStyle = {height: this.frame.height - bottomHeight, flex: 0};
|
||||
heightStyle = {
|
||||
height: this._frame.height - bottomHeight,
|
||||
flex: 0,
|
||||
};
|
||||
}
|
||||
return (
|
||||
// $FlowFixMe - Typing ReactNativeComponent revealed errors
|
||||
<View
|
||||
ref={viewRef}
|
||||
style={[style, heightStyle]}
|
||||
style={StyleSheet.compose(style, heightStyle)}
|
||||
onLayout={this._onLayout}
|
||||
{...props}>
|
||||
{children}
|
||||
@@ -200,29 +179,26 @@ const KeyboardAvoidingView = ((createReactClass({
|
||||
);
|
||||
|
||||
case 'position':
|
||||
const positionStyle = {bottom: bottomHeight};
|
||||
const {contentContainerStyle} = this.props;
|
||||
|
||||
return (
|
||||
// $FlowFixMe - Typing ReactNativeComponent revealed errors
|
||||
<View
|
||||
ref={viewRef}
|
||||
style={style}
|
||||
onLayout={this._onLayout}
|
||||
{...props}>
|
||||
<View style={[contentContainerStyle, positionStyle]}>
|
||||
<View
|
||||
style={StyleSheet.compose(contentContainerStyle, {
|
||||
bottom: bottomHeight,
|
||||
})}>
|
||||
{children}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
case 'padding':
|
||||
const paddingStyle = {paddingBottom: bottomHeight};
|
||||
return (
|
||||
// $FlowFixMe - Typing ReactNativeComponent revealed errors
|
||||
<View
|
||||
ref={viewRef}
|
||||
style={[style, paddingStyle]}
|
||||
style={StyleSheet.compose(style, {paddingBottom: bottomHeight})}
|
||||
onLayout={this._onLayout}
|
||||
{...props}>
|
||||
{children}
|
||||
@@ -231,7 +207,6 @@ const KeyboardAvoidingView = ((createReactClass({
|
||||
|
||||
default:
|
||||
return (
|
||||
// $FlowFixMe - Typing ReactNativeComponent revealed errors
|
||||
<View
|
||||
ref={viewRef}
|
||||
onLayout={this._onLayout}
|
||||
@@ -241,7 +216,7 @@ const KeyboardAvoidingView = ((createReactClass({
|
||||
</View>
|
||||
);
|
||||
}
|
||||
},
|
||||
}): any): React.ComponentType<Props>);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = KeyboardAvoidingView;
|
||||
|
||||
Reference in New Issue
Block a user