diff --git a/android/src/main/java/com/swmansion/reanimated/keyboard/Keyboard.java b/android/src/main/java/com/swmansion/reanimated/keyboard/Keyboard.java index 0f8ae78071e..d71bd5f0725 100644 --- a/android/src/main/java/com/swmansion/reanimated/keyboard/Keyboard.java +++ b/android/src/main/java/com/swmansion/reanimated/keyboard/Keyboard.java @@ -1,7 +1,5 @@ package com.swmansion.reanimated.keyboard; -import android.view.KeyCharacterMap; -import android.view.KeyEvent; import androidx.core.view.WindowInsetsCompat; import com.facebook.react.uimanager.PixelUtil; @@ -23,9 +21,7 @@ public int getHeight() { public void updateHeight(WindowInsetsCompat insets) { int contentBottomInset = insets.getInsets(CONTENT_TYPE_MASK).bottom; int systemBarBottomInset = insets.getInsets(SYSTEM_BAR_TYPE_MASK).bottom; - boolean hasNavigationBar = KeyCharacterMap.deviceHasKey(KeyEvent.KEYCODE_HOME); - int keyboardHeightDip = - hasNavigationBar ? contentBottomInset - systemBarBottomInset : contentBottomInset; + int keyboardHeightDip = contentBottomInset - systemBarBottomInset; int keyboardHeight = (int) PixelUtil.toDIPFromPixel(Math.max(0, keyboardHeightDip)); if (keyboardHeight == 0 && mState == KeyboardState.OPEN) { /* @@ -35,7 +31,7 @@ public void updateHeight(WindowInsetsCompat insets) { */ return; } - mHeight = (int) PixelUtil.toDIPFromPixel(keyboardHeightDip); + mHeight = keyboardHeight; } public void onAnimationStart() { diff --git a/android/src/main/java/com/swmansion/reanimated/keyboard/WindowsInsetsManager.java b/android/src/main/java/com/swmansion/reanimated/keyboard/WindowsInsetsManager.java index b7ad125e9c8..353084cf042 100644 --- a/android/src/main/java/com/swmansion/reanimated/keyboard/WindowsInsetsManager.java +++ b/android/src/main/java/com/swmansion/reanimated/keyboard/WindowsInsetsManager.java @@ -9,7 +9,6 @@ import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsCompat; import com.facebook.react.bridge.ReactApplicationContext; -import com.swmansion.reanimated.BuildConfig; import java.lang.ref.WeakReference; public class WindowsInsetsManager { @@ -58,24 +57,19 @@ private void updateWindowDecor(boolean decorFitsSystemWindow) { } private WindowInsetsCompat onApplyWindowInsetsListener(View view, WindowInsetsCompat insets) { + WindowInsetsCompat defaultInsets = ViewCompat.onApplyWindowInsets(view, insets); if (mKeyboard.getState() == KeyboardState.OPEN) { mKeyboard.updateHeight(insets); mNotifyAboutKeyboardChange.call(); } - setWindowInsets(insets); - return insets; + setWindowInsets(defaultInsets); + return defaultInsets; } private void setWindowInsets(WindowInsetsCompat insets) { - int paddingBottom = 0; - boolean isOldPaperImplementation = - !BuildConfig.IS_NEW_ARCHITECTURE_ENABLED && BuildConfig.REACT_NATIVE_MINOR_VERSION < 70; - if (isOldPaperImplementation) { - int navigationBarTypeMask = WindowInsetsCompat.Type.navigationBars(); - paddingBottom = insets.getInsets(navigationBarTypeMask).bottom; - } int systemBarsTypeMask = WindowInsetsCompat.Type.systemBars(); int paddingTop = insets.getInsets(systemBarsTypeMask).top; + int paddingBottom = insets.getInsets(systemBarsTypeMask).bottom; updateInsets(paddingTop, paddingBottom); } @@ -95,7 +89,7 @@ private FrameLayout.LayoutParams getLayoutParams(int paddingTop, int paddingBott FrameLayout.LayoutParams params = new FrameLayout.LayoutParams(matchParentFlag, matchParentFlag); if (mIsStatusBarTranslucent) { - params.setMargins(0, 0, 0, 0); + params.setMargins(0, 0, 0, paddingBottom); } else { params.setMargins(0, paddingTop, 0, paddingBottom); } diff --git a/docs/docs/device/useAnimatedKeyboard.mdx b/docs/docs/device/useAnimatedKeyboard.mdx index d337062a4c0..0a723f0c52e 100644 --- a/docs/docs/device/useAnimatedKeyboard.mdx +++ b/docs/docs/device/useAnimatedKeyboard.mdx @@ -2,72 +2,119 @@ sidebar_position: 1 --- -:::info -This page was ported from an old version of the documentation. - -As we're rewriting the documentation some of the pages might be a little outdated. -::: +`useAnimatedKeyboard` lets you create animations based on state and height of the virtual keyboard. :::caution -Android implementation of `useAnimatedKeyboard` is an experimental feature. +Android implementation of `useAnimatedKeyboard` has drawbacks on Android SDK < 30, for more details see [remarks](/docs/device/useAnimatedKeyboard#remarks) section. ::: -With the `useAnimatedKeyboard` hook, you can create animations based on current keyboard position. +## Reference + +```javascript +import { useAnimatedKeyboard, useAnimatedStyle } from 'react-native-reanimated'; + +export default function App() { + const keyboard = useAnimatedKeyboard(); + + const animatedStyles = useAnimatedStyle(() => ({ + transform: [{ translateY: -keyboard.height.value }], + })); +} +``` + +
+Type definitions + +```typescript +// --- Function declaration --- + +function useAnimatedKeyboard( + options: AnimatedKeyboardOptions +): AnimatedKeyboardInfo; + +// --- Configuration types --- + +export interface AnimatedKeyboardOptions { + isStatusBarTranslucentAndroid?: boolean; +} + +// --- Return types --- -On Android, make sure to set `android:windowSoftInputMode` in your `AndroidManifest.xml` to `adjustResize`. Then, using the `useAnimatedKeyboard` hook disables -the default Android behavior (resizing the view to accommodate keyboard) in the whole app. Using values from `useAnimatedKeyboard` hook you can handle the keyboard yourself. Unmounting all components that use `useAnimatedKeyboard` hook brings back the default Android behavior. +export type AnimatedKeyboardInfo = { + height: SharedValue; + state: SharedValue; +}; + +export enum KeyboardState { + UNKNOWN = 0, + OPENING = 1, + OPEN = 2, + CLOSING = 3, + CLOSED = 4, +} +``` + +
### Arguments -#### `options` [AnimatedKeyboardOptions] +#### `options` -Optional object containing additional configuration. +Optional object containing additional configuration: + +- `isStatusBarTranslucentAndroid` - removes top inset on Android i.e. to use translucent status bar on Android, set this option to `true`. Defaults to `false`. Ignored on iOS. ### Returns -Hook `useAnimatedKeyboard` returns an instance of [[AnimatedKeyboardInfo](#animatedkeyboard-object)]; +Hook `useAnimatedKeyboard` returns an object containing these fields: -### Types +| Name | Type | Description | +| ------ | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | +| height | `SharedValue` | A [shared value](/docs/fundamentals/glossary#shared-value) containing current height of the keyboard. | +| state | `SharedValue` | A [shared value](/docs/fundamentals/glossary#shared-value) containing current state of the keyboard. Possible states: `{ CLOSED, OPEN, CLOSING, OPENING }` | -#### `AnimatedKeyboardInfo: [object]` +## Example -Properties: +import useBaseUrl from '@docusaurus/useBaseUrl'; +import AnimatedKeyboardSrc from '!!raw-loader!@site/src/examples/AnimatedKeyboard'; -- `height`: [[SharedValue](/docs/core/useSharedValue)] contains `[number]` - contains current height of the keyboard -- `state`: [[SharedValue](/docs/core/useSharedValue)] contains `[enum]` - contains current state of the keyboard. Possible states: `{ CLOSED, OPEN, CLOSING, OPENING }` + + } +/> -#### `AnimatedKeyboardOptions: [object]` +## Remarks -Properties: +- On Android, make sure to set `android:windowSoftInputMode` in your `AndroidManifest.xml` to `adjustResize`. Then, using the `useAnimatedKeyboard` hook disables + the default Android behavior (resizing the view to accommodate keyboard) in the whole app. Using values from `useAnimatedKeyboard` hook you can handle the keyboard yourself. Unmounting all components that use `useAnimatedKeyboard` hook brings back the default Android behavior. -- `isStatusBarTranslucentAndroid`[bool] - if you want to use translucent status bar on Android, set this option to `true`. Defaults to `false`. Ignored on iOS. +- On Android, using the `useAnimatedKeyboard` hook expands root view to full screen ([immersive mode](https://developer.android.com/develop/ui/views/layout/immersive)) and takes control over insets management. -### Example + - When `isStatusBarTranslucentAndroid` is `false` it applies the top and bottom margins according to the insets. -```jsx -function AnimatedKeyboardExample() { - const keyboard = useAnimatedKeyboard(); - const translateStyle = useAnimatedStyle(() => { - return { - transform: [{ translateY: -keyboard.height.value }], - }; - }); - - return ( - - - - - - ); -} -``` + - When `isStatusBarTranslucentAndroid` is `true` it applies bottom padding according to the navigation inset and sets top margin to `0`. + +- On Android, when using navigation with native header, `isStatusBarTranslucentAndroid` doesn't affect the top inset. + +- On Android SDK < 30, when status bar is hidden, the keyboard reverts to the default Android behavior. + +## Platform compatibility + +
+ +| Android | iOS | Web | +| ------- | --- | --- | +| ✅ | ✅ | ❌ | + +
diff --git a/docs/src/components/ThemedVideo/index.tsx b/docs/src/components/ThemedVideo/index.tsx index 84947504458..4b14b5e922d 100644 --- a/docs/src/components/ThemedVideo/index.tsx +++ b/docs/src/components/ThemedVideo/index.tsx @@ -8,18 +8,21 @@ interface Props { light: string; dark: string; }; + center?: boolean; + width?: number; } export default function ThemedVideo(props: Props) { const { sources } = props; return ( -
+
@@ -28,6 +31,7 @@ export default function ThemedVideo(props: Props) { autoPlay muted loop + width={props.width} className={clsx(styles.themedVideo, styles[`themedVideo--light`])}> diff --git a/docs/src/components/ThemedVideo/styles.module.css b/docs/src/components/ThemedVideo/styles.module.css index 2310210836c..5b6d778e615 100644 --- a/docs/src/components/ThemedVideo/styles.module.css +++ b/docs/src/components/ThemedVideo/styles.module.css @@ -3,6 +3,11 @@ margin: '16px 0'; } +.center { + display: flex; + justify-content: center; +} + .themedVideo { display: none; } diff --git a/docs/src/examples/AnimatedKeyboard.tsx b/docs/src/examples/AnimatedKeyboard.tsx new file mode 100644 index 00000000000..712001b0514 --- /dev/null +++ b/docs/src/examples/AnimatedKeyboard.tsx @@ -0,0 +1,46 @@ +import React from 'react'; +import Animated, { + useAnimatedKeyboard, + useAnimatedStyle, +} from 'react-native-reanimated'; +import { StyleSheet, TextInput, View, useColorScheme } from 'react-native'; + +export default function App() { + const colorScheme = useColorScheme(); + const keyboard = useAnimatedKeyboard(); + + const animatedStyles = useAnimatedStyle(() => ({ + transform: [{ translateY: -keyboard.height.value }], + })); + + return ( + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: 'center', + borderWidth: 5, + borderColor: '#782aeb', + borderRadius: 2, + }, + box: { + justifyContent: 'center', + alignItems: 'center', + height: 200, + backgroundColor: '#b58df1', + borderRadius: 5, + margin: 20, + }, +}); diff --git a/docs/static/recordings/useAnimatedKeyboard_dark.mov b/docs/static/recordings/useAnimatedKeyboard_dark.mov new file mode 100644 index 00000000000..198d4496170 Binary files /dev/null and b/docs/static/recordings/useAnimatedKeyboard_dark.mov differ diff --git a/docs/static/recordings/useAnimatedKeyboard_light.mov b/docs/static/recordings/useAnimatedKeyboard_light.mov new file mode 100644 index 00000000000..dabddff0ddc Binary files /dev/null and b/docs/static/recordings/useAnimatedKeyboard_light.mov differ