issue animating the react navigation native stack header
Asked Answered
Y

1

7

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",
Yippee answered 16/12, 2022 at 7:19 Comment(0)
B
7

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!

Brough answered 18/12, 2022 at 23:5 Comment(2)
I'm getting this issue adding your suggested implementation: Error: Maximum update depth exceeded. it is difficult to share all actual component code but the error is throwing when I use the header property inside setOptions... if I set any other property, this error is not thrown.Cherub
Usually, this happens when we make state changes repeatedly, and consequently, multiple re-renders happen. Does your header component update any state?Brough

© 2022 - 2024 — McMap. All rights reserved.