react-native-reanimated
react-native-reanimated copied to clipboard
Android: useAnimatedKeyboard listener breaks translucent StatusBar + NavigationBar
Description
When using useAnimatedKeyboard
applications that take advantage of the full screen by using translucent (absolutely positioned) StatusBar and NavigationBar revert to having solid counterparts. (Full reproduction repo below, no need to follow the steps)
Steps to reproduce
- Instantiate a React Native project.
- Create the following methods in
MainActivity.java
:
private void transparentStatusAndNavigation() {
//make full transparent statusBar
if (Build.VERSION.SDK_INT >= 19 && Build.VERSION.SDK_INT < 21) {
setWindowFlag(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, true);
}
if (Build.VERSION.SDK_INT >= 19) {
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
);
}
if (Build.VERSION.SDK_INT >= 21) {
setWindowFlag(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS
| WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, false);
getWindow().setStatusBarColor(Color.TRANSPARENT);
getWindow().setNavigationBarColor(Color.TRANSPARENT);
}
}
private void setWindowFlag(final int bits, boolean on) {
Window win = getWindow();
WindowManager.LayoutParams winParams = win.getAttributes();
if (on) {
winParams.flags |= bits;
} else {
winParams.flags &= ~bits;
}
win.setAttributes(winParams);
}
- Call from
onCreate
:
@Override
protected void onCreate(Bundle savedInstanceState) {
// Rest of logic here
transparentStatusAndNavigation();
}
- Remember to add new imports:
import android.graphics.Color;
import android.view.Window;
import android.view.WindowManager;
import android.view.View;
- Use
useAnimatedKeyboard
and the insets will disappear.
Snack or a link to a repository
https://github.com/ChildishForces/reanimated-bug-repro
Reanimated version
2.10.0
React Native version
0.69.0
Platforms
Android
JavaScript runtime
Hermes
Workflow
Expo bare workflow
Architecture
Paper (Old Architecture)
Build type
Debug mode
Device
Real device
Device model
OnePlus 9 Pro
Acknowledgements
Yes
FYI: From my basic investigation I think the following lines may be the culprit
I tried to clone, fix and build, but I think there is something different about the build process of 2.10.0 where remote sources/binaries are included. If someone were to explain how to disable this to me, I'd be happy to implement a check to store the initial value and return it to that, rather than true
, and submit a PR.
There are many things connected to this.
Are you using expo or expo-dev-client? Are you using react-native-screens and the native stack? If so, make sure you're at minimum version 3.14+. There are still some cases were the old deprecated window
flag manipulation is used (even in core react-native). This breaks every usage of the Windows Insets API.
I am using https://github.com/kirillzyusko/react-native-keyboard-controller, which provides basically the same hook. @kirillzyusko and I spent a ton of time to track down this issues and his library also exposes a statusBarTranslucent prop on the provider to fix all of this weird issues. You could give it a try.
I'll do that and report back, though would love to see this resolved in reanimated, as I like to keep my dependencies as few as possible.
@hirbod Sorry, forgot to update! Your provided solution worked perfectly, thanks for your help. Going to leave this issue open, so when the RNR team get round to it they have a summary & repro of the issue for their own hook.
I have the same bug. Here is a reproducible code snippet. I'm using the latest RN (0.70.4) and reanimated (2.12.0)
import React from 'react';
import {Button, View, StyleSheet, StatusBar} from 'react-native';
import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import {useAnimatedKeyboard} from 'react-native-reanimated';
function HomeScreen({navigation}) {
return (
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'green',
}}>
<Button
title="Go to Profile"
onPress={() => navigation.navigate('Notifications')}
/>
</View>
);
}
function NotificationsScreen({navigation}) {
const keyboard = useAnimatedKeyboard(); // <=====
return (
<View
style={{
flex: 1,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: 'blue',
}}>
<Button title="Go back" onPress={() => navigation.goBack()} />
</View>
);
}
const Stack = createNativeStackNavigator();
function MyStack() {
return (
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Notifications" component={NotificationsScreen} />
</Stack.Navigator>
);
}
export default function App() {
return (
<GestureHandlerRootView style={{flex: 1, backgroundColor: 'red'}}>
<StatusBar
barStyle="dark-content"
hidden={false}
translucent={true}
backgroundColor="transparent"
/>
<NavigationContainer>
<MyStack />
</NavigationContainer>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 24,
backgroundColor: 'blue',
},
contentContainer: {
flex: 1,
alignItems: 'center',
},
});