-
-
Notifications
You must be signed in to change notification settings - Fork 530
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
fix: calculate values of useHeaderHeight natively (#1802)
## Description Currently, `useHeaderHeight()` hook returns default values, hard-coded in JS code. It uses `react-native-safe-area-context` under the hood to calculate top inset of the status bar and returns a header height `56` (for android) or `64` (for iOS). This PR introduces new `useAnimatedHeaderHeight` hook that calculates header height on each layout change. ## Changes - Added support for calculating header height dynamically on the old / new architecture for Android and iOS. - Added event that is being executed on header height change. - Added new `useAnimatedHeaderHeight` hook for getting header height that is being calculated dynamically. ## Screenshots / GIFs ### Before https://github.com/software-mansion/react-native-screens/assets/23281839/1e129ce8-cc62-4333-a6d8-f7283f018441 ### After https://github.com/software-mansion/react-native-screens/assets/23281839/5c11f371-c3f7-4eda-9969-4eb02f9de26a ## Test code and steps to reproduce See `Test1802` in `TestsExample` or `FabricTestExample`. There's also `Test1802b` included. It's a reproduction for an issue #1671 which shows that issue should be fixed after this change. ## Checklist - [X] Included code example that can be used to test this change - [X] Updated TS types - [X] Updated documentation: - [X] https://github.com/software-mansion/react-native-screens/blob/main/guides/GUIDE_FOR_LIBRARY_AUTHORS.md - [X] https://github.com/software-mansion/react-native-screens/blob/main/native-stack/README.md - [X] https://github.com/software-mansion/react-native-screens/blob/main/src/types.tsx - [x] Ensured that CI passes
- Loading branch information
Showing
28 changed files
with
997 additions
and
66 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import React from 'react'; | ||
import { View } from 'react-native'; | ||
import { | ||
useHeaderHeight, | ||
createNativeStackNavigator, | ||
} from 'react-native-screens/native-stack'; | ||
import { NavigationContainer } from '@react-navigation/native'; | ||
|
||
const App = () => { | ||
const backgroundStyle = { | ||
backgroundColor: '#fafffe', // isDarkMode ? Colors.darker : Colors.lighter, | ||
}; | ||
|
||
return ( | ||
<View | ||
style={[ | ||
backgroundStyle, | ||
{ | ||
flex: 1, | ||
}, | ||
]}> | ||
<View | ||
style={{ | ||
flex: 1, | ||
height: '100%', | ||
}}> | ||
<Navigation /> | ||
</View> | ||
</View> | ||
); | ||
}; | ||
|
||
const List = () => { | ||
const headerHeight = useHeaderHeight(); | ||
|
||
return ( | ||
<View style={{ flex: 1, backgroundColor: '#00fffa' }}> | ||
<View | ||
style={{ | ||
backgroundColor: '#fffa00', | ||
position: 'absolute', | ||
top: headerHeight, | ||
width: 200, | ||
height: 100, | ||
}} | ||
/> | ||
</View> | ||
); | ||
}; | ||
|
||
const Stack = createNativeStackNavigator(); | ||
|
||
const Navigation = () => { | ||
return ( | ||
<NavigationContainer> | ||
<Stack.Navigator | ||
screenOptions={{ | ||
fullScreenSwipeEnabled: true, | ||
stackAnimation: 'fade_from_bottom', | ||
customAnimationOnSwipe: true, | ||
// headerLargeTitle: true, | ||
headerTranslucent: true, | ||
}}> | ||
<Stack.Screen | ||
name="Header" | ||
component={List} | ||
options={{ statusBarStyle: 'dark' }} | ||
/> | ||
</Stack.Navigator> | ||
</NavigationContainer> | ||
); | ||
}; | ||
|
||
export default App; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,174 @@ | ||
import React from 'react'; | ||
import { Dimensions, Image, StyleSheet, Text, View } from 'react-native'; | ||
import { NavigationContainer, ParamListBase } from '@react-navigation/native'; | ||
|
||
import { | ||
createNativeStackNavigator, | ||
NativeStackNavigationProp, | ||
} from 'react-native-screens/native-stack'; | ||
|
||
// Uncomment these lines if you want to test useAnimatedHeaderHeight. | ||
import { Animated } from 'react-native'; | ||
import { useAnimatedHeaderHeight } from 'react-native-screens/native-stack'; | ||
|
||
// Uncomment these lines if you want to test useReanimatedHeaderHeight. | ||
// import Animated from 'react-native-reanimated'; | ||
// import { useReanimatedHeaderHeight } from 'react-native-screens/reanimated'; | ||
|
||
import { | ||
GestureHandlerRootView, | ||
ScrollView, | ||
State, | ||
TapGestureHandler, | ||
} from 'react-native-gesture-handler'; | ||
import { ReanimatedScreenProvider } from 'react-native-screens/reanimated'; | ||
import { FullWindowOverlay } from 'react-native-screens'; | ||
|
||
const Stack = createNativeStackNavigator(); | ||
|
||
function ExampleScreen() { | ||
const headerHeight = useAnimatedHeaderHeight(); | ||
// const headerHeight = useReanimatedHeaderHeight(); | ||
|
||
return ( | ||
<FullWindowOverlay> | ||
<Animated.View | ||
style={{ | ||
display: 'flex', | ||
justifyContent: 'flex-end', | ||
alignItems: 'center', | ||
|
||
backgroundColor: 'red', | ||
width: '100%', | ||
opacity: 0.5, | ||
height: 60, | ||
zIndex: 1, | ||
transform: [ | ||
{ | ||
translateY: headerHeight, | ||
}, | ||
], | ||
}}> | ||
<Text>I'm a header!</Text> | ||
</Animated.View> | ||
</FullWindowOverlay> | ||
); | ||
} | ||
|
||
const enablePerformanceTests = false; | ||
|
||
function First({ | ||
navigation, | ||
}: { | ||
navigation: NativeStackNavigationProp<ParamListBase>; | ||
}) { | ||
return ( | ||
<ScrollView contentInsetAdjustmentBehavior="automatic"> | ||
<ExampleScreen /> | ||
<Post onPress={() => navigation.navigate('Second')} /> | ||
<Post onPress={() => navigation.navigate('Second')} /> | ||
<Post onPress={() => navigation.navigate('Second')} /> | ||
{ | ||
// Generate 1000 posts for performance testing. | ||
enablePerformanceTests && | ||
Array(1000) | ||
.fill(0) | ||
.map(_ => <Post onPress={() => navigation.navigate('Second')} />) | ||
} | ||
</ScrollView> | ||
); | ||
} | ||
|
||
function Second() { | ||
return ( | ||
<ScrollView> | ||
<ExampleScreen /> | ||
<Text style={styles.subTitle}> | ||
Use swipe back gesture to go back (iOS only) | ||
</Text> | ||
<Post /> | ||
</ScrollView> | ||
); | ||
} | ||
|
||
export default function App() { | ||
return ( | ||
<GestureHandlerRootView style={{ flex: 1 }}> | ||
<ReanimatedScreenProvider> | ||
<NavigationContainer> | ||
<Stack.Navigator | ||
screenOptions={{ | ||
fullScreenSwipeEnabled: true, | ||
stackAnimation: 'fade_from_bottom', | ||
customAnimationOnSwipe: true, | ||
headerLargeTitle: true, | ||
// headerTranslucent: true, | ||
}}> | ||
<Stack.Screen name="First" component={First} /> | ||
<Stack.Screen name="Second" component={Second} /> | ||
</Stack.Navigator> | ||
</NavigationContainer> | ||
</ReanimatedScreenProvider> | ||
</GestureHandlerRootView> | ||
); | ||
} | ||
|
||
// components | ||
|
||
function Post({ onPress }: { onPress?: () => void }) { | ||
const [width] = React.useState(Math.round(Dimensions.get('screen').width)); | ||
|
||
return ( | ||
<TapGestureHandler | ||
onHandlerStateChange={e => | ||
e.nativeEvent.oldState === State.ACTIVE && onPress?.() | ||
}> | ||
<View style={styles.post}> | ||
<Text style={styles.title}>Post</Text> | ||
<ScrollView horizontal>{generatePhotos(4, width, 400)}</ScrollView> | ||
<Text style={styles.caption}>Scroll right for more photos</Text> | ||
</View> | ||
</TapGestureHandler> | ||
); | ||
} | ||
|
||
// helpers | ||
function generatePhotos( | ||
amount: number, | ||
width: number, | ||
height: number, | ||
): JSX.Element[] { | ||
const startFrom = Math.floor(Math.random() * 20) + 10; | ||
return Array.from({ length: amount }, (_, index) => { | ||
const uri = `https://picsum.photos/id/${ | ||
startFrom + index | ||
}/${width}/${height}`; | ||
return <Image style={{ width, height }} key={uri} source={{ uri }} />; | ||
}); | ||
} | ||
|
||
const styles = StyleSheet.create({ | ||
title: { | ||
fontWeight: 'bold', | ||
fontSize: 32, | ||
marginBottom: 8, | ||
marginLeft: 8, | ||
}, | ||
subTitle: { | ||
fontSize: 18, | ||
marginVertical: 16, | ||
textAlign: 'center', | ||
}, | ||
caption: { | ||
textAlign: 'center', | ||
marginTop: 4, | ||
}, | ||
post: { | ||
borderColor: '#ccc', | ||
borderTopWidth: 1, | ||
borderBottomWidth: 1, | ||
paddingVertical: 10, | ||
marginBottom: 16, | ||
backgroundColor: 'white', | ||
}, | ||
}); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
import React from 'react'; | ||
import { View } from 'react-native'; | ||
import { | ||
useHeaderHeight, | ||
createNativeStackNavigator, | ||
} from 'react-native-screens/native-stack'; | ||
import { NavigationContainer } from '@react-navigation/native'; | ||
|
||
const App = () => { | ||
const backgroundStyle = { | ||
backgroundColor: '#fafffe', // isDarkMode ? Colors.darker : Colors.lighter, | ||
}; | ||
|
||
return ( | ||
<View | ||
style={[ | ||
backgroundStyle, | ||
{ | ||
flex: 1, | ||
}, | ||
]}> | ||
<View | ||
style={{ | ||
flex: 1, | ||
height: '100%', | ||
}}> | ||
<Navigation /> | ||
</View> | ||
</View> | ||
); | ||
}; | ||
|
||
const List = () => { | ||
const headerHeight = useHeaderHeight(); | ||
|
||
return ( | ||
<View style={{ flex: 1, backgroundColor: '#00fffa' }}> | ||
<View | ||
style={{ | ||
backgroundColor: '#fffa00', | ||
position: 'absolute', | ||
top: headerHeight, | ||
width: 200, | ||
height: 100, | ||
}} | ||
/> | ||
</View> | ||
); | ||
}; | ||
|
||
const Stack = createNativeStackNavigator(); | ||
|
||
const Navigation = () => { | ||
return ( | ||
<NavigationContainer> | ||
<Stack.Navigator | ||
screenOptions={{ | ||
fullScreenSwipeEnabled: true, | ||
stackAnimation: 'fade_from_bottom', | ||
customAnimationOnSwipe: true, | ||
// headerLargeTitle: true, | ||
headerTranslucent: true, | ||
}}> | ||
<Stack.Screen | ||
name="Header" | ||
component={List} | ||
options={{ statusBarStyle: 'dark' }} | ||
/> | ||
</Stack.Navigator> | ||
</NavigationContainer> | ||
); | ||
}; | ||
|
||
export default App; |
Oops, something went wrong.