UNPKG

hcmobile-sdk

Version:

mobile-sdk

375 lines (348 loc) 12.3 kB
/* @flow github https://github.com/APSL/react-native-keyboard-aware-scroll-view */ import React from 'react'; import PropTypes from 'prop-types'; import ReactNative, { Keyboard, Platform, UIManager, TextInput, Dimensions } from 'react-native'; import { KeyboardAwareInterface } from './KeyboardAwareInterface'; const _KAM_DEFAULT_TAB_BAR_HEIGHT: number = isIphoneX() ? 83 : 49; const _KAM_KEYBOARD_OPENING_TIME: number = 250; const _KAM_EXTRA_HEIGHT: number = 75; export function isIphoneX() { const dimen = Dimensions.get('window'); return ( Platform.OS === 'ios' && !Platform.isPad && !Platform.isTVOS && (dimen.height === 812 || dimen.width === 812) ); } export function ifIphoneX(iphoneXStyle, regularStyle) { if (isIphoneX()) { return iphoneXStyle; } return regularStyle; } export type KeyboardAwareHOCProps = { viewIsInsideTabBar?: boolean, resetScrollToCoords?: { x: number, y: number }, enableResetScrollToCoords?: boolean, enableAutomaticScroll?: boolean, extraHeight?: number, extraScrollHeight?: number, keyboardOpeningTime?: number, onScroll?: Function, contentContainerStyle?: any, enableOnAndroid?: boolean, innerRef?: Function } export type KeyboardAwareHOCState = { keyboardSpace: number } function listenToKeyboardEvents(ScrollableComponent: React$Component) { return class extends React.Component< KeyboardAwareHOCProps, KeyboardAwareHOCState > implements KeyboardAwareInterface { _rnkasv_keyboardView: any keyboardWillShowEvent: ?Function keyboardWillHideEvent: ?Function position: { x: number, y: number } defaultResetScrollToCoords: ?{ x: number, y: number } resetCoords: ?{ x: number, y: number } mountedComponent: boolean handleOnScroll: Function state: KeyboardAwareHOCState static propTypes = { viewIsInsideTabBar: PropTypes.bool, resetScrollToCoords: PropTypes.shape({ x: PropTypes.number.isRequired, y: PropTypes.number.isRequired }), enableResetScrollToCoords: PropTypes.bool, enableAutomaticScroll: PropTypes.bool, extraHeight: PropTypes.number, extraScrollHeight: PropTypes.number, keyboardOpeningTime: PropTypes.number, onScroll: PropTypes.func, contentContainerStyle: PropTypes.any, enableOnAndroid: PropTypes.bool, innerRef: PropTypes.func } static defaultProps = { enableAutomaticScroll: true, extraHeight: _KAM_EXTRA_HEIGHT, extraScrollHeight: 0, enableResetScrollToCoords: true, keyboardOpeningTime: _KAM_KEYBOARD_OPENING_TIME, viewIsInsideTabBar: false } constructor(props: KeyboardAwareHOCProps) { super(props) this.keyboardWillShowEvent = undefined this.keyboardWillHideEvent = undefined this.position = { x: 0, y: 0 } this.defaultResetScrollToCoords = null const keyboardSpace: number = props.viewIsInsideTabBar ? _KAM_DEFAULT_TAB_BAR_HEIGHT : 0 this.state = { keyboardSpace } } componentDidMount() { this.mountedComponent = true // Keyboard events if (Platform.OS === 'ios') { this.keyboardWillShowEvent = Keyboard.addListener( 'keyboardWillShow', this._updateKeyboardSpace ) this.keyboardWillHideEvent = Keyboard.addListener( 'keyboardWillHide', this._resetKeyboardSpace ) } else if (Platform.OS === 'android' && this.props.enableOnAndroid) { this.keyboardWillShowEvent = Keyboard.addListener( 'keyboardDidShow', this._updateKeyboardSpace ) this.keyboardWillHideEvent = Keyboard.addListener( 'keyboardDidHide', this._resetKeyboardSpace ) } } componentWillReceiveProps(nextProps: KeyboardAwareHOCProps) { if (nextProps.viewIsInsideTabBar !== this.props.viewIsInsideTabBar) { const keyboardSpace: number = nextProps.viewIsInsideTabBar ? _KAM_DEFAULT_TAB_BAR_HEIGHT : 0 if (this.state.keyboardSpace !== keyboardSpace) { this.setState({ keyboardSpace }) } } } componentWillUnmount() { this.mountedComponent = false this.keyboardWillShowEvent && this.keyboardWillShowEvent.remove() this.keyboardWillHideEvent && this.keyboardWillHideEvent.remove() } getScrollResponder = () => { return ( this._rnkasv_keyboardView && this._rnkasv_keyboardView.getScrollResponder() ) } scrollToPosition = (x: number, y: number, animated: boolean = true) => { const responder = this.getScrollResponder() responder && responder.scrollResponderScrollTo({ x, y, animated }) } scrollToEnd = (animated?: boolean = true) => { const responder = this.getScrollResponder() responder && responder.scrollResponderScrollToEnd({ animated }) } scrollForExtraHeightOnAndroid = (extraHeight: number) => { this.scrollToPosition(0, this.position.y + extraHeight, true) } /** * @param keyboardOpeningTime: takes a different keyboardOpeningTime in consideration. * @param extraHeight: takes an extra height in consideration. */ scrollToFocusedInput = ( reactNode: any, extraHeight?: number, keyboardOpeningTime?: number ) => { if (extraHeight === undefined) { extraHeight = this.props.extraHeight || 0 } if (keyboardOpeningTime === undefined) { keyboardOpeningTime = this.props.keyboardOpeningTime || 0 } setTimeout(() => { if (!this.mountedComponent) { return } const responder = this.getScrollResponder() responder && responder.scrollResponderScrollNativeHandleToKeyboard( reactNode, extraHeight, true ) }, keyboardOpeningTime) } // Keyboard actions _updateKeyboardSpace = (frames: Object) => { // Automatically scroll to focused TextInput if (this.props.enableAutomaticScroll) { let keyboardSpace: number = frames.endCoordinates.height + this.props.extraScrollHeight if (this.props.viewIsInsideTabBar) { keyboardSpace -= _KAM_DEFAULT_TAB_BAR_HEIGHT } this.setState({ keyboardSpace }) const currentlyFocusedField = TextInput.State.currentlyFocusedField() const responder = this.getScrollResponder() if (!currentlyFocusedField || !responder) { return } UIManager.viewIsDescendantOf( currentlyFocusedField, responder.getInnerViewNode(), (isAncestor: boolean) => { if (isAncestor) { // Check if the TextInput will be hidden by the keyboard UIManager.measureInWindow( currentlyFocusedField, (x: number, y: number, width: number, height: number) => { const textInputBottomPosition = y + height const keyboardPosition = frames.endCoordinates.screenY const totalExtraHeight = this.props.extraScrollHeight + this.props.extraHeight if (Platform.OS === 'ios') { if ( textInputBottomPosition > keyboardPosition - totalExtraHeight ) { this._scrollToFocusedInputWithNodeHandle( currentlyFocusedField ) } } else { // On android, the system would scroll the text input just // above the keyboard so we just neet to scroll the extra // height part if (textInputBottomPosition > keyboardPosition) { // Since the system already scrolled the whole view up // we should reduce that amount keyboardSpace = keyboardSpace - (textInputBottomPosition - keyboardPosition) this.setState({ keyboardSpace }) this.scrollForExtraHeightOnAndroid(totalExtraHeight) } else if ( textInputBottomPosition > keyboardPosition - totalExtraHeight ) { this.scrollForExtraHeightOnAndroid( totalExtraHeight - (keyboardPosition - textInputBottomPosition) ) } } } ) } } ) } if (!this.resetCoords) { if (!this.defaultResetScrollToCoords) { this.defaultResetScrollToCoords = this.position } } } _resetKeyboardSpace = () => { const keyboardSpace: number = this.props.viewIsInsideTabBar ? _KAM_DEFAULT_TAB_BAR_HEIGHT + this.props.extraScrollHeight || 0 : this.props.extraScrollHeight || 0 this.setState({ keyboardSpace }) // Reset scroll position after keyboard dismissal if (this.props.enableResetScrollToCoords === false) { this.defaultResetScrollToCoords = null return } else if (this.resetCoords) { this.scrollToPosition(this.resetCoords.x, this.resetCoords.y, true) } else { if (this.defaultResetScrollToCoords) { this.scrollToPosition( this.defaultResetScrollToCoords.x, this.defaultResetScrollToCoords.y, true ) this.defaultResetScrollToCoords = null } else { this.scrollToPosition(0, 0, true) } } } _scrollToFocusedInputWithNodeHandle = ( nodeID: number, extraHeight?: number, keyboardOpeningTime?: number ) => { if (extraHeight === undefined) { extraHeight = this.props.extraHeight } const reactNode = ReactNative.findNodeHandle(nodeID) this.scrollToFocusedInput( reactNode, extraHeight + this.props.extraScrollHeight, keyboardOpeningTime !== undefined ? keyboardOpeningTime : this.props.keyboardOpeningTime || 0 ) } _handleOnScroll = ( e: SyntheticEvent<*> & { nativeEvent: { contentOffset: number } } ) => { this.position = e.nativeEvent.contentOffset } _handleRef = (ref: React.Component<*>) => { this._rnkasv_keyboardView = ref if (this.props.innerRef) { this.props.innerRef(this._rnkasv_keyboardView) } } _onScroll = ( e: SyntheticEvent<*> & { nativeEvent: { contentOffset: number } } ) => { this._handleOnScroll(e) this.props.onScroll && this.props.onScroll(e) } render() { const { enableOnAndroid, contentContainerStyle } = this.props let newContentContainerStyle if (Platform.OS === 'android' && enableOnAndroid) { newContentContainerStyle = [].concat(contentContainerStyle).concat({ paddingBottom: ((contentContainerStyle || {}).paddingBottom || 0) + this.state.keyboardSpace }) } return ( <ScrollableComponent ref={this._handleRef} keyboardDismissMode='interactive' contentInset={{ bottom: this.state.keyboardSpace }} automaticallyAdjustContentInsets={false} showsVerticalScrollIndicator={true} scrollEventThrottle={1} {...this.props} contentContainerStyle={ newContentContainerStyle || contentContainerStyle } keyboardSpace={this.state.keyboardSpace} getScrollResponder={this.getScrollResponder} scrollToPosition={this.scrollToPosition} scrollToEnd={this.scrollToEnd} scrollForExtraHeightOnAndroid={this.scrollForExtraHeightOnAndroid} scrollToFocusedInput={this.scrollToFocusedInput} resetKeyboardSpace={this._resetKeyboardSpace} handleOnScroll={this._handleOnScroll} onScroll={this._onScroll} /> ) } } } export default listenToKeyboardEvents