Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Update useAnimatedKeyboard docs #5866

Merged
merged 11 commits into from May 9, 2024
@@ -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;

Expand All @@ -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) {
/*
Expand All @@ -35,7 +31,7 @@ public void updateHeight(WindowInsetsCompat insets) {
*/
return;
}
mHeight = (int) PixelUtil.toDIPFromPixel(keyboardHeightDip);
mHeight = keyboardHeight;
}

public void onAnimationStart() {
Expand Down
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
}
Expand Down
132 changes: 86 additions & 46 deletions docs/docs/device/useAnimatedKeyboard.mdx
Expand Up @@ -2,72 +2,112 @@
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 }],
}));
}
```

<details>
<summary>Type definitions</summary>

```typescript
// --- Function declaration ---

function useAnimatedKeyboard(
options: AnimatedKeyboardOptions
): AnimatedKeyboardInfo;

// --- Configuration 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 interface AnimatedKeyboardOptions {
isStatusBarTranslucentAndroid?: boolean;
}

// --- Return types ---

export type AnimatedKeyboardInfo = {
height: SharedValue<number>;
state: SharedValue<KeyboardState>;
};

export enum KeyboardState {
UNKNOWN = 0,
OPENING = 1,
OPEN = 2,
CLOSING = 3,
CLOSED = 4,
}
```

</details>

### Arguments

#### `options` [AnimatedKeyboardOptions]
#### `options` <Optional />

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<number>` | A [shared value](/docs/fundamentals/glossary#shared-value) containing current height of the keyboard. |
| state | `SharedValue<KeyboardState>` | 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 }`
<InteractiveExample
src={AnimatedKeyboardSrc}
component={
<ThemedVideo
center
width={300}
sources={{
light: '/recordings/useAnimatedKeyboard_light.mov',
dark: '/recordings/useAnimatedKeyboard_dark.mov',
}}
/>
}
/>

#### `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.
- On Android, using the `useAnimatedKeyboard` hook expands root view to full screen (immersive mode) and takes control over insets management. It applies the top and bottom margins according to the insets when `isStatusBarTranslucentAndroid` is false and sets top margin to 0 and bottom padding according to the navigation insets when `isStatusBarTranslucentAndroid` is true.
maciekstosio marked this conversation as resolved.
Show resolved Hide resolved
- 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.
maciekstosio marked this conversation as resolved.
Show resolved Hide resolved

- `isStatusBarTranslucentAndroid`[bool] - if you want to use translucent status bar on Android, set this option to `true`. Defaults to `false`. Ignored on iOS.
## Platform compatibility

### Example
<div className="platform-compatibility">

```jsx
function AnimatedKeyboardExample() {
const keyboard = useAnimatedKeyboard();
const translateStyle = useAnimatedStyle(() => {
return {
transform: [{ translateY: -keyboard.height.value }],
};
});

return (
<ScrollView
contentContainerStyle={{
flex: 1,
justifyContent: 'center',
alignItems: 'center',
}}>
<Animated.View style={translateStyle}>
<TextInput />
</Animated.View>
</ScrollView>
);
}
```
| Android | iOS | Web |
| ------- | --- | --- |
| ✅ | ✅ | ❌ |

</div>
6 changes: 5 additions & 1 deletion docs/src/components/ThemedVideo/index.tsx
Expand Up @@ -8,18 +8,21 @@ interface Props {
light: string;
dark: string;
};
center?: boolean;
width?: number;
}

export default function ThemedVideo(props: Props) {
const { sources } = props;

return (
<div className={styles.container}>
<div className={clsx(styles.container, props.center && styles.center)}>
<video
playsInline
autoPlay
muted
loop
width={props.width}
className={clsx(styles.themedVideo, styles[`themedVideo--dark`])}>
<source src={useBaseUrl(sources.dark)} />
</video>
Expand All @@ -28,6 +31,7 @@ export default function ThemedVideo(props: Props) {
autoPlay
muted
loop
width={props.width}
className={clsx(styles.themedVideo, styles[`themedVideo--light`])}>
<source src={useBaseUrl(sources.light)} />
</video>
Expand Down
5 changes: 5 additions & 0 deletions docs/src/components/ThemedVideo/styles.module.css
Expand Up @@ -3,6 +3,11 @@
margin: '16px 0';
}

.center {
display: flex;
justify-content: center;
}

.themedVideo {
display: none;
}
Expand Down
46 changes: 46 additions & 0 deletions 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 (
<Animated.View
style={[
styles.container,
animatedStyles,
{ backgroundColor: colorScheme === 'light' ? '#fff' : '#000' },
]}>
<View style={styles.box}>
<TextInput placeholder="Text Input" />
</View>
</Animated.View>
);
}

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,
},
});
Binary file not shown.
Binary file not shown.