diff --git a/README.md b/README.md index 81da024..225e3f5 100644 --- a/README.md +++ b/README.md @@ -142,6 +142,7 @@ Use `innerRef` to get the component reference and use `this.scrollRef.props` to | `getScrollResponder` | `void` | Get `ScrollResponder` | | `scrollToPosition` | `x: number, y: number, animated: bool = true` | Scroll to specific position with or without animation. | | `scrollToEnd` | `animated?: bool = true` | Scroll to end with or without animation. | +| `scrollIntoView` | `element: React.Element<*>, options: { getScrollPosition: ?(parentLayout, childLayout, contentOffset) => { x: number, y: number, animated: boolean } }` | Scrolls an element inside a KeyboardAwareScrollView into view. | ### Using high order component Enabling any component to be keyboard-aware is very easy. Take a look at the code of `KeyboardAwareListView`: diff --git a/lib/KeyboardAwareHOC.js b/lib/KeyboardAwareHOC.js index 6bbe295..4108adc 100644 --- a/lib/KeyboardAwareHOC.js +++ b/lib/KeyboardAwareHOC.js @@ -6,7 +6,8 @@ import ReactNative, { Keyboard, Platform, UIManager, - TextInput + TextInput, + findNodeHandle } from 'react-native' import { isIphoneX } from 'react-native-iphone-x-helper' @@ -36,6 +37,32 @@ export type KeyboardAwareHOCState = { keyboardSpace: number } +export type ElementLayout = { + x: number, + y: number, + width: number, + height: number +} + +export type ContentOffset = { + x: number, + y: number +} + +export type ScrollPosition = { + x: number, + y: number, + animated: boolean +} + +export type ScrollIntoViewOptions = ?{ + getScrollPosition?: ( + parentLayout: ElementLayout, + childLayout: ElementLayout, + contentOffset: ContentOffset + ) => ScrollPosition +} + function listenToKeyboardEvents(ScrollableComponent: React$Component) { return class extends React.Component< KeyboardAwareHOCProps, @@ -44,7 +71,7 @@ function listenToKeyboardEvents(ScrollableComponent: React$Component) { _rnkasv_keyboardView: any keyboardWillShowEvent: ?Function keyboardWillHideEvent: ?Function - position: { x: number, y: number } + position: ContentOffset defaultResetScrollToCoords: ?{ x: number, y: number } resetCoords: ?{ x: number, y: number } mountedComponent: boolean @@ -180,6 +207,48 @@ function listenToKeyboardEvents(ScrollableComponent: React$Component) { }, keyboardOpeningTime) } + scrollIntoView = async ( + element: React.Element<*>, + options: ScrollIntoViewOptions = {} + ) => { + if (!this._rnkasv_keyboardView || !element) { + return + } + + const [ + parentLayout, + childLayout + ] = await Promise.all([ + this._measureElement(this._rnkasv_keyboardView), + this._measureElement(element) + ]) + + const getScrollPosition = options.getScrollPosition || this._defaultGetScrollPosition + const { x, y, animated } = getScrollPosition(parentLayout, childLayout, this.position) + this.scrollToPosition(x, y, animated) + } + + _defaultGetScrollPosition = ( + parentLayout: ElementLayout, + childLayout: ElementLayout, + contentOffset: ContentOffset + ): ScrollPosition => { + return { + x: 0, + y: Math.max(0, childLayout.y - parentLayout.y + contentOffset.y), + animated: true, + } + } + + _measureElement = (element: React.Element<*>): Promise => { + const node = findNodeHandle(element) + return new Promise((resolve: (ElementLayout) => void) => { + UIManager.measureInWindow(node, (x: number, y: number, width: number, height: number) => { + resolve({ x, y, width, height }) + }) + }) + } + // Keyboard actions _updateKeyboardSpace = (frames: Object) => { // Automatically scroll to focused TextInput