Reputation: 2781
I'm trying to animate the react navigation native stack header by changing the background from a transparent to a gray color when the user scrolls down. I was reading the documentation and it suggests using the navigation.setOptions to interact with the screen info.
I am using react-native-reanimated to capture the scroll value and change it when the user interacts with the screen.
I'm capturing the scroll value and using it inside the setOptions
method but it doesn't work, it just doesn't execute the changes.
import React from 'react';
import {
useAnimatedScrollHandler,
useSharedValue,
} from 'react-native-reanimated';
const MyScreen = ({ navigation }) => {
const scrollY = useSharedValue(0);
const scrollHandler = useAnimatedScrollHandler({
onScroll: (e) => {
scrollY.value = e.contentOffset.y;
},
});
React.useLayoutEffect(() => {
navigation.setOptions({
headerStyle: {
backgroundColor: scrollY.value > 0 ? 'black' : 'transparent',
},
headerTransparent: scrollY.value === 0,
});
}, [ navigation, scrollY.value ]);
}
"react-native": "0.67.2",
"react-native-reanimated": "2.9.1",
"@react-navigation/native": "^6.0.6",
"@react-navigation/native-stack": "^6.2.5",
Upvotes: 7
Views: 2064
Reputation: 548
It's possible to animate the Native Stack header, but since only Animated
components accept animated styles in Reanimated 2, you'd probably have to create a new component for the header (an Animated.Something
)...
We can achieve this by using the header
option, which can be found here.
A simple example built with Expo:
import React from "react";
import { StatusBar } from "expo-status-bar";
import { StyleSheet, Text, View } from "react-native";
import { NavigationContainer, useNavigation } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import Animated, {
useSharedValue,
useAnimatedStyle,
useAnimatedScrollHandler,
interpolateColor,
} from "react-native-reanimated";
const Stack = createNativeStackNavigator();
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "white",
},
item: {
padding: 20,
margin: 15,
backgroundColor: "whitesmoke",
},
header: {
paddingTop: 50,
padding: 15,
borderColor: "whitesmoke",
borderBottomWidth: 1,
},
headerTitle: {
fontSize: 20,
fontWeight: "bold",
},
});
function WelcomeScreen() {
const navigation = useNavigation();
const translationY = useSharedValue(0);
const scrollHandler = useAnimatedScrollHandler((event) => {
translationY.value = event.contentOffset.y;
});
const aStyle = useAnimatedStyle(() => ({
backgroundColor: interpolateColor(
translationY.value,
[0, 50],
["white", "skyblue"],
"RGB"
),
}));
React.useLayoutEffect(() => {
navigation.setOptions({
header: () => (
<Animated.View style={[styles.header, aStyle]}>
<Text style={styles.headerTitle}>Testing</Text>
</Animated.View>
),
});
}, [aStyle, navigation]);
return (
<View style={styles.container}>
<Animated.ScrollView onScroll={scrollHandler} scrollEventThrottle={16}>
{Array(15)
.fill(0)
.map((_, index) => (
<View style={styles.item} key={`${index}`}>
<Text>Item {`${index}`}</Text>
</View>
))}
</Animated.ScrollView>
<StatusBar style="auto" />
</View>
);
}
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen component={WelcomeScreen} name="Welcome" />
</Stack.Navigator>
</NavigationContainer>
);
}
Note that in this example, we're passing an Animated.View
to the header
option, passing our animated style (aStyle
) to it as a style.
Also, just like you did, I'm using useAnimatedScrollHandler
to track the scroll position, and interpolating (with interpolateColor
) the backgroundColor
of the header accordingly (between 'white' and 'skyblue', so it's easier to visualize).
Uploaded this example to this Snack so you can easily test it if you want.
I Hope this helps you solve your problem!
Upvotes: 7