Cristian Flórez
Cristian Flórez

Reputation: 2781

issue animating the react navigation native stack header

What I want

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.

The problem

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 ]);
}

Deps

"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

Answers (1)

joaocout
joaocout

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

Related Questions