Skip to content

Commit 4982d4c

Browse files
fix(Android): header config shadow node has wrong origin when translucent == true (#3239)
## Description #2466 fixes Pressable in screen header by shifting the HeaderConfigShadowNode origin.y by its frame height. (more info in the pull request). However, setting isTranslucent to true on android displays the header over the content so we must not apply this correction. This fixes Pressable for translucent headers on Android. ## Changes Skip the frame corrections on Android when translucent is true. I'm not sure that's the cleanest way to fix this issue, but i'm not familiar enough with react-native-screens to find a better solution. Because this is an issue I have in my production app and this is critical, I'm pushing this fix for now. ## Test code and steps to reproduce https://snack.expo.dev/@dernise/header-navigation-broken (Run this on a real device by scanning the QR code in expo go, do not use the emulator provided by appetize) --------- Co-authored-by: Kacper Kafara <kacperkafara@gmail.com>
1 parent 47e45a6 commit 4982d4c

File tree

3 files changed

+101
-0
lines changed

3 files changed

+101
-0
lines changed

apps/src/tests/Test3239.tsx

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import { NavigationContainer } from '@react-navigation/native';
2+
import { createNativeStackNavigator, NativeStackNavigationProp } from '@react-navigation/native-stack';
3+
import React from 'react';
4+
import { findNodeHandle, Text, View } from 'react-native';
5+
import PressableWithFeedback from '../shared/PressableWithFeedback';
6+
7+
type StackParamList = {
8+
Home: undefined,
9+
}
10+
11+
type RouteProps = {
12+
navigation: NativeStackNavigationProp<StackParamList>;
13+
}
14+
15+
const Stack = createNativeStackNavigator<StackParamList>();
16+
17+
function HeaderTitle(): React.JSX.Element {
18+
return (
19+
<PressableWithFeedback
20+
onLayout={event => {
21+
const { x, y, width, height } = event.nativeEvent.layout;
22+
console.log('Title onLayout', { x, y, width, height });
23+
}}
24+
onPressIn={() => {
25+
console.log('Pressable onPressIn');
26+
}}
27+
onPress={() => console.log('Pressable onPress')}
28+
onPressOut={() => console.log('Pressable onPressOut')}
29+
onResponderMove={() => console.log('Pressable onResponderMove')}
30+
ref={node => {
31+
console.log(findNodeHandle(node));
32+
node?.measure((x, y, width, height, pageX, pageY) => {
33+
console.log('header component measure', { x, y, width, height, pageX, pageY });
34+
});
35+
}}
36+
>
37+
<View style={{ height: 40, justifyContent: 'center', alignItems: 'center' }}>
38+
<Text style={{ alignItems: 'center' }}>Regular Pressable</Text>
39+
</View>
40+
</PressableWithFeedback>
41+
);
42+
}
43+
44+
function HeaderLeft(): React.JSX.Element {
45+
return (
46+
<HeaderTitle />
47+
);
48+
}
49+
50+
function Home(_: RouteProps): React.JSX.Element {
51+
return (
52+
<View style={{ flex: 1, backgroundColor: 'rgba(0, 0, 0, .8)' }}
53+
>
54+
<View style={{ flex: 1, alignItems: 'center', justifyContent: 'center',marginTop: 48 }}>
55+
<PressableWithFeedback
56+
onPressIn={() => console.log('Pressable onPressIn')}
57+
onPress={() => console.log('Pressable onPress')}
58+
onPressOut={() => console.log('Pressable onPressOut')}
59+
>
60+
<View style={{ height: 40, width: 200, justifyContent: 'center', alignItems: 'center' }}>
61+
<Text style={{ alignItems: 'center' }}>Regular Pressable</Text>
62+
</View>
63+
</PressableWithFeedback>
64+
</View>
65+
</View>
66+
);
67+
}
68+
69+
function App(): React.JSX.Element {
70+
return (
71+
<NavigationContainer>
72+
<Stack.Navigator>
73+
<Stack.Screen
74+
name="Home"
75+
component={Home}
76+
options={{
77+
headerTransparent: true,
78+
headerTitle: HeaderTitle,
79+
headerLeft: HeaderLeft,
80+
headerRight: HeaderLeft,
81+
}}
82+
/>
83+
</Stack.Navigator>
84+
</NavigationContainer>
85+
);
86+
}
87+
88+
export default App;

apps/src/tests/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,7 @@ export { default as Test3111 } from './Test3111';
149149
export { default as Test3115 } from './Test3115';
150150
export { default as Test3168 } from './Test3168';
151151
export { default as Test3173 } from './Test3173';
152+
export { default as Test3239 } from './Test3239';
152153
export { default as TestScreenAnimation } from './TestScreenAnimation';
153154
export { default as TestScreenAnimationV5 } from './TestScreenAnimationV5';
154155
export { default as TestHeader } from './TestHeader';

common/cpp/react/renderer/components/rnscreens/RNSScreenStackHeaderConfigShadowNode.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,18 @@ extern const char RNSScreenStackHeaderConfigComponentName[] =
77

88
void RNSScreenStackHeaderConfigShadowNode::layout(LayoutContext layoutContext) {
99
YogaLayoutableShadowNode::layout(layoutContext);
10+
11+
#if defined(ANDROID)
12+
const auto &headerProps =
13+
*std::static_pointer_cast<const RNSScreenStackHeaderConfigProps>(
14+
this->getProps());
15+
if (headerProps.translucent) {
16+
// On Android, when header is translucent, the Screen is laid out underneath
17+
// the native header view, therefore HeaderConfig origin already matches
18+
// the toolbar & the correction is not needed.
19+
return;
20+
}
21+
#endif // ANDROID
1022
applyFrameCorrections();
1123
}
1224

0 commit comments

Comments
 (0)