Skip to content

Commit

Permalink
Merge pull request #2 from iway1/1/easing-prop
Browse files Browse the repository at this point in the history
Added easing prop
  • Loading branch information
iway1 authored Aug 29, 2022
2 parents be9b60b + 5fa0d78 commit f6fffbc
Show file tree
Hide file tree
Showing 8 changed files with 86 additions and 78 deletions.
1 change: 1 addition & 0 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { KeyboardAwareScrollView } from 'react-native-keyboard-aware-scroll-view
import KeyboardAvoiderScrollSection from './package/src/components/KeyboardAvoiderScrollSection';
import ReactNativeExample, { ReactNativeFixedExample } from './examples/react-native';
import { textInputStyle } from './examples/styles';
import { Easing } from 'react-native-reanimated';

const inputStyle: ViewStyle = {
padding: 50,
Expand Down
38 changes: 22 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -276,28 +276,34 @@ For developer convenience, this repo is a React Native project. You can just pul

## Props

### Common props
All of the components support the following props:

| **Props** | **Type** | **Description** | **Default** |
|-----------------|-------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------|
| animationTime | number | Time to move the view out of the way when the keyboard opens. | 150 |
| animationEasing | Animated.EasingFunction | Easing function to use for both opening and closing animation.<br>Opening animation will use `Easing.out(animationEasing)`, while closing animation<br>will use `Easing.in(animationEasing)`. | Easing.quad |
| extraSpace | number | How much space there should be between the keyboard avoiding element and the keyboard. | 20 |

### `KeyboardAvoiderView` Props
All `View` props will be passed. Additionally, the following props can be passed:
Supports all React Native `View` props as well as all [common props](#common-keyboardavoider-props).

Additionally, it supports the following props

| **Props** | **Type** | **Description** | **Default** |
|---------------|---------------------------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| avoidMode | 'whole-view'<br>'focused-input' | Which avoid mode to use.<br>'whole-view' will move the entire view out of the way.<br>'focused-input' will move just enough to show the input plus the extra space. | 'whole-view' |
| enableAndroid | boolean | Enable keyboard avoiding on Android. | true |

| **Prop** | **Type** | **Description** | **Default** |
|---------------|---------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------|--------------|
| animationTime | number | Time to move the view out of the way when the keyboard opens. | 150 |
| avoidMode | 'whole-view'<br>'focused-input' | Which avoid mode to use. <br>'whole-view' will move the entire `KeyboardAvoidingView` out of the way.<br>'focused-input' will only show the focused input. | 'whole-view' |
| enableAndroid | boolean | Enable keyboard avoiding on android. | true |
| extraSpace | number | How much space there should be between the keyboard avoiding element and the keyboard. | 20 |

### `KeyboardAvoiderScrollView` Props
All `ScrollView` props can be passed. Additionally, the following props can be passed:
Supports all React Native `ScrollView` props as well as all [common props](#common-keyboardavoider-props).

Additionally, it supports the following props

| **Props** | **Type** | **Description** | **Default** |
|-----------------|--------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-------------|
| extraSpace | number | How much space there should be between the keyboard avoiding element and the keyboard. | 20 |
| animationTime | number | Time to move the view out of the way when the keyboard opens. | 150 |
| iosHideBehavior | 'stay'<br>'revert' | What to do when the keyboard hides on iOS.<br>'stay' makes it where the scrollview stays where it is when the keyboard closes.<br>'revert' makes it where the scrollview returns to its original position. | 'stay' |
| iosHideBehavior | 'stay'<br>'revert' | Behavior when the keyboard hides on iOS.<br>'stay' makes it where the scroll view stays where it is when the keyboard closes.<br>'revert' makes it where the scroll view returns to its original position. | 'stay' |

### `KeyboardAvoiderInsets` Props

| **Props** | **Type** | **Description** | **Default** |
|---------------|----------|----------------------------------------------------------------------------------------|-------------|
| extraSpace | number | How much space there should be between the keyboard avoiding element and the keyboard. | 20 |
| animationTime | number | Time to move the view out of the way when the keyboard opens. | 150 |
Supports all [common props](#common-keyboardavoider-props).
31 changes: 13 additions & 18 deletions package/src/components/KeyboardAvoiderInsets.tsx
Original file line number Diff line number Diff line change
@@ -1,24 +1,16 @@
import React from "react";
import { useRef } from "react";
import { Platform } from "react-native";
import Animated, { useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
import { DEFAULT_ANIMATION_TIME, DEFAULT_EXTRA_SPACE } from "../defaults";
import Animated, { EasingFn, useAnimatedStyle, useSharedValue, withTiming } from "react-native-reanimated";
import { useKeyboardHandlers } from "../hooks";
import { calcAndroidSystemPan, closeAnimation, measureFocusedInputBottomY, openAnimation } from "../utilities";
import { CommonProps, defaultCommonProps } from "./common-props";

export default function KeyboardAvoiderInsets({
animationTime = DEFAULT_ANIMATION_TIME,
extraSpace = DEFAULT_EXTRA_SPACE,
}: {
/**
* Duration of the keyboard avoiding animation.
*/
animationTime?: number,
/**
* Extra space between the keyboard avoiding element and the keyboard.
*/
extraSpace?: number,
}) {
animationEasing=defaultCommonProps.animationEasing,
animationTime=defaultCommonProps.animationTime,
extraSpace=defaultCommonProps.extraSpace
}:CommonProps) {
const heightAnimatedValue = useSharedValue<number>(0);
const animatedRef = useRef<Animated.View | null>(null);

Expand All @@ -31,30 +23,33 @@ export default function KeyboardAvoiderInsets({
useKeyboardHandlers({
showHandler: (e) => {
if (Platform.OS == 'android') {
measureFocusedInputBottomY((inputBottomY)=>{
measureFocusedInputBottomY((inputBottomY) => {
const systemPan = calcAndroidSystemPan({
inputBottomY,
keyboardEndY: e.endCoordinates.screenY,
})
animatedRef.current?.measure((x, y, width, height, pageX, pageY) => {
const delta = Math.max(0, (pageY + extraSpace) - e.endCoordinates.screenY - systemPan);
if (delta) {
heightAnimatedValue.value = withTiming(delta, openAnimation(animationTime))
heightAnimatedValue.value = withTiming(delta, {
easing: animationEasing,
duration: animationTime
})
}
})
})
} else {
animatedRef.current?.measure((x, y, width, height, pageX, pageY) => {
const delta = Math.max(0, (pageY + extraSpace) - e.endCoordinates.screenY);
if (delta) {
heightAnimatedValue.value = withTiming(delta, openAnimation(animationTime))
heightAnimatedValue.value = withTiming(delta, openAnimation(animationTime, animationEasing))
}
})
}

},
hideHandler: () => {
heightAnimatedValue.value = withTiming(0, closeAnimation(animationTime))
heightAnimatedValue.value = withTiming(0, closeAnimation(animationTime, animationEasing))
}
})

Expand Down
26 changes: 8 additions & 18 deletions package/src/components/KeyboardAvoiderScrollView.tsx
Original file line number Diff line number Diff line change
@@ -1,27 +1,16 @@
import React, { createContext, useContext, useMemo, useRef, useState } from "react";
import { KeyboardEventListener, NativeScrollEvent, NativeSyntheticEvent, Platform, ScrollViewProps, View, ScrollView } from "react-native";
import Animated, { Easing, Layout, scrollTo, useAnimatedRef, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming } from "react-native-reanimated";
import { DEFAULT_ANIMATION_TIME, DEFAULT_EXTRA_SPACE } from "../defaults";
import Animated, { scrollTo, useAnimatedRef, useAnimatedStyle, useDerivedValue, useSharedValue, withTiming } from "react-native-reanimated";
import { useKeyboardHandlers } from "../hooks";
import { closeAnimation, measureFocusedInputBottomYAsync } from "../utilities";
import { CommonProps, defaultCommonProps } from "./common-props";

const ScrollContext = createContext<{
registerView: ({view, id,}:{view: View, id: string}) => void,
unregisterView: (id: string)=>void,
} | null>(null);

interface Props extends ScrollViewProps {
/**
* Extra space between the keyboard and the keyboard avoiding element.
* Defaults to 20.
*/
extraSpace?: number,

/**
* Duration of the keyboard avoiding animation.
*/
animationTime?: number,

type Props = ScrollViewProps & CommonProps & {
/**
* What to do when the keyboard hides on iOS.
* @option 'stay' - *Default* scroll view will not move when the keyboard hides (it will stay where it is.)
Expand All @@ -39,8 +28,9 @@ async function measureView(view: View) {
}

export default function KeyboardAvoiderScrollView({
extraSpace = DEFAULT_EXTRA_SPACE,
animationTime=DEFAULT_ANIMATION_TIME,
animationEasing=defaultCommonProps.animationEasing,
animationTime=defaultCommonProps.animationTime,
extraSpace=defaultCommonProps.extraSpace,
iosHideBehavior='stay',
...props
}: Props) {
Expand Down Expand Up @@ -105,7 +95,7 @@ export default function KeyboardAvoiderScrollView({

function handleKeyboardWillHide() {
if(Platform.OS == 'android' || iosHideBehavior == 'revert') {
yTranslate.value = withTiming(0, closeAnimation(animationTime))
yTranslate.value = withTiming(0, closeAnimation(animationTime, animationEasing))
return;
}
const scrollsToAdjustedForTranslate = currentScroll.current - yTranslate.value;
Expand All @@ -114,7 +104,7 @@ export default function KeyboardAvoiderScrollView({
if(scrollsToAdjustedForTranslate >= scrollMax.current) {
// In cases where there is no room to actually scroll the scroll view we just animate back to the
// start position.
yTranslate.value = withTiming(0, closeAnimation(animationTime))
yTranslate.value = withTiming(0, closeAnimation(animationTime, animationEasing))
return;
}

Expand Down
31 changes: 12 additions & 19 deletions package/src/components/KeyboardAvoiderView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,31 +13,24 @@ import Animated, {
useSharedValue,
withTiming,
} from 'react-native-reanimated'
import { DEFAULT_ANIMATION_TIME, DEFAULT_EXTRA_SPACE } from '../defaults'
import { useKeyboardHandlers } from '../hooks'
import { closeAnimation, measureFocusedInputBottomY, openAnimation } from '../utilities'
import { CommonProps, defaultCommonProps } from './common-props'

export type KeyboardAvoidMode = 'whole-view' | 'focused-input';

export default function KeyboardAvoiderView({
animationEasing = defaultCommonProps.animationEasing,
animationTime = defaultCommonProps.animationTime,
extraSpace = defaultCommonProps.extraSpace,
enableAndroid = true,
animationTime = DEFAULT_ANIMATION_TIME,
extraSpace = DEFAULT_EXTRA_SPACE,
avoidMode = 'whole-view',
...props
}: ComponentProps<typeof Animated.View> & {
}: ComponentProps<typeof Animated.View> & CommonProps & {
/**
* Enable on android. Defaults to true to ensure consistent behavior.
*/
enableAndroid?: boolean,
/**
* Sets the duration of the keyboard avoiding animation.
*/
animationTime?: number,
/**
* Extra space between the keyboard avoiding element and the keyboard.
*/
extraSpace?: number,
/**
* Sets the avoid mode. Defaults to 'whole-view'.
* @option 'whole-view' - view moves to show the entire view when the keyboard is shown.
Expand All @@ -52,12 +45,12 @@ export default function KeyboardAvoiderView({

const handleKeyboardWillShow: KeyboardEventListener = (e) => {
keyboardTopRef.current = e.endCoordinates.screenY

if (Platform.OS == 'android') {
measureFocusedInputBottomY((inputBottomY) => {
const pannedBy = Math.max(inputBottomY - e.endCoordinates.screenY, 0);
if(avoidMode=='focused-input') {
setScrollToElementBottomY(inputBottomY-pannedBy)
if (avoidMode == 'focused-input') {
setScrollToElementBottomY(inputBottomY - pannedBy)
return;
}
ref.current?.measure((x, y, w, viewHeight, px, viewPageY) => {
Expand All @@ -79,7 +72,7 @@ export default function KeyboardAvoiderView({
})
}
}

function handleKeyboardWillHide() {
setScrollToElementBottomY(null)
}
Expand All @@ -91,18 +84,18 @@ export default function KeyboardAvoiderView({
}, [avoidMode, extraSpace, animationTime])

function pos(scrollTo: number) {
return scrollTo - keyboardTopRef.current + DEFAULT_EXTRA_SPACE
return scrollTo - keyboardTopRef.current + extraSpace
}

useEffect(() => {
if (scrollToElementBottomY === null) {
animation.value = withTiming(0, closeAnimation(animationTime))
animation.value = withTiming(0, closeAnimation(animationTime, animationEasing))
return
}
const p = pos(scrollToElementBottomY);
if (p <= 0) return
if (animation.value !== p) {
animation.value = withTiming(p, openAnimation(animationTime))
animation.value = withTiming(p, openAnimation(animationTime, animationEasing))
}
}, [scrollToElementBottomY])

Expand Down
23 changes: 23 additions & 0 deletions package/src/components/common-props.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import Animated, { Easing } from "react-native-reanimated"

export type CommonProps = {
/**
* Duration of the keyboard avoiding animation.
*/
animationTime?: number,
/**
* Extra space between the keyboard avoiding element and the keyboard.
*/
extraSpace?: number,
/**
* Easing function to use. Can be any `react-native-reanimated` easing function. Defaults to Easing.quad.
* Open animation will use Easing.out(animationEasing), close animation will use Easing.in(animationEasing).
*/
animationEasing?: Animated.EasingFunction
}

export const defaultCommonProps = {
animationTime: 150,
animationEasing: Easing.quad,
extraSpace: 20,
}
4 changes: 2 additions & 2 deletions package/src/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export const DEFAULT_EXTRA_SPACE = 20
export const DEFAULT_ANIMATION_TIME = 150
import { Easing } from "react-native-reanimated"

10 changes: 5 additions & 5 deletions package/src/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TextInput } from "react-native"
import { Easing } from "react-native-reanimated";
import Animated, { Easing } from "react-native-reanimated";

export async function measureFocusedInputBottomYAsync() {
return new Promise<number>(resolve=>{
Expand Down Expand Up @@ -36,16 +36,16 @@ export function calcAndroidSystemPan({
return Math.max(0, delta);
}

export function closeAnimation(duration: number) {
export function closeAnimation(duration: number, easing: Animated.EasingFunction) {
return {
duration: duration + 50,
easing: Easing.in(Easing.ease)
easing: Easing.in(easing)
}
}

export function openAnimation(duration: number) {
export function openAnimation(duration: number, easing: Animated.EasingFunction) {
return {
duration,
easing: Easing.inOut(Easing.ease)
easing: Easing.out(easing)
}
}

0 comments on commit f6fffbc

Please sign in to comment.