Skip to content

Instantly share code, notes, and snippets.

@vovkasm
Created November 30, 2018 05:47
Show Gist options
  • Save vovkasm/8e2d2f6ac9b77e16a802e2900b9f9e40 to your computer and use it in GitHub Desktop.
Save vovkasm/8e2d2f6ac9b77e16a802e2900b9f9e40 to your computer and use it in GitHub Desktop.
Sample of custom analog of KeyboardAvoidingView in "padding" mode, that actually works.
import React from 'react'
import {
EmitterSubscription,
Keyboard,
LayoutAnimation,
LayoutChangeEvent,
LayoutRectangle,
Platform,
StyleSheet,
View,
ViewProps,
} from 'react-native'
// iOS: all events:
// keyboardWillChangeFrame, keyboardDidChangeFrame,
// keyboardWillShow, keyboardDidShow,
// keyboardWillHide and keyboardDidHide
// all events contains all data
// Android: only two events keyboardDidShow and keyboardDidHide
// keyboardDidShow - only data is endCoordinates
// keyboardDidHide - data is null
interface IKeyboardEvent {
easing?: string
duration?: number
startCoordinates?: IKeyboardRectangle
endCoordinates?: IKeyboardRectangle
}
interface IKeyboardRectangle {
screenX: number
screenY: number
width: number
height: number
}
interface IState {
padding: number
}
/**
* View that moves out of the way when the keyboard appears by automatically
* adjusting its height, position, or bottom padding.
*/
export class KeyboardAvoidingView extends React.Component<ViewProps, IState> {
state: IState = { padding: 0 }
private _subscriptions: EmitterSubscription[] = []
private viewRef: View | null | undefined = undefined
private viewFrame: LayoutRectangle | undefined = undefined
private keyboardFrame: IKeyboardRectangle | undefined = undefined
componentDidMount(): void {
if (Platform.OS === 'ios') {
this._subscriptions = [Keyboard.addListener('keyboardWillChangeFrame', this.onKeyboardChange)]
} else if (Platform.OS === 'android') {
this._subscriptions = [
Keyboard.addListener('keyboardDidShow', this.onKeyboardChange),
Keyboard.addListener('keyboardDidHide', this.onKeyboardChange),
]
} else {
throw new Error('KeyboardAvoidingView supports only ios and android platforms')
}
}
componentWillUnmount(): void {
this._subscriptions.forEach((subscription) => {
subscription.remove()
})
}
render() {
return (
<View
ref={this.setViewRef}
{...this.props}
style={StyleSheet.flatten([this.props.style, { paddingBottom: this.state.padding }])}
onLayout={this.onViewLayout}
/>
)
}
private setViewRef: React.Ref<View> = (ref) => {
this.viewRef = ref
}
private onViewLayout = (ev: LayoutChangeEvent) => {
if (!this.viewRef) return
this.viewRef.measureInWindow(this.onViewMeasure)
}
private onViewMeasure = (x: number, y: number, width: number, height: number) => {
this.viewFrame = { x, y, width, height }
this.updatePadding()
}
private updatePadding() {
const padding = this.calculatePadding()
if (this.state.padding !== padding) {
this.setState({ padding })
}
}
private calculatePadding() {
if (!this.viewFrame || !this.keyboardFrame) return 0
const keyboardTop = this.keyboardFrame.screenY
const viewBottom = this.viewFrame.y + this.viewFrame.height
return keyboardTop < viewBottom ? viewBottom - keyboardTop : 0
}
private onKeyboardChange = (ev: IKeyboardEvent) => {
// KeyboardDidHide on android
if (!ev) {
this.keyboardFrame = undefined
this.updatePadding()
return
}
this.keyboardFrame = ev.endCoordinates
if (ev.duration) {
LayoutAnimation.configureNext({
duration: ev.duration,
update: { duration: ev.duration, type: ev.easing || 'keyboard' },
})
}
this.updatePadding()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment