React-native-reanimated tab menu: do I need position absolute?

132 Views Asked by At

I would like to create an animated tab menu to learn react-native-reanimated. Here is my full code:

import { View, StyleSheet, Text, Pressable, Dimensions } from "react-native";
    import Animated, {
      useAnimatedStyle,
      useSharedValue,
      withSpring,
    } from "react-native-reanimated";
    
    
    const numberOfTabs = 3;
    const windowWidth = Dimensions.get("window").width;
    const tabWith = windowWidth / numberOfTabs;
    
    export default function App() {
      const position = useSharedValue(0);
    
      const indexArray = [];
      for (let index = 0; index < numberOfTabs; index++) {
        indexArray.push(index);
      }
    
      const selectedButtonAnimatedStyle = useAnimatedStyle(() => {
        return {
          position: "absolute",
          left: position.value,
          right: 0,
          top: 0,
          width: tabWith,
        };
      });
    
      const handlePress = (index) => {
        position.value = withSpring(tabWith * index);
      };
    
      return (
        <View style={styles.container}>
          <View style={styles.buttonGroup}>
            <Animated.View
              style={[styles.selectedButton, selectedButtonAnimatedStyle]}
            />
            {indexArray.map((i) => (
              <Pressable
                key={i}
                style={styles.menuButton}
                onPress={() => handlePress(i)}>
                <Text>category {i}</Text>
              </Pressable>
            ))}
          </View>
        </View>
      );
    }
    
    const styles = StyleSheet.create({
      menuButton: {
        width: tabWith,
        alignItems: "center",
        justifyContent: "center",
      },
      selectedButton: {
        backgroundColor: "green",
        height: "100%",
      },
      buttonGroup: {
        flex: 1,
        width: "100%",
        flexDirection: "row",
        maxHeight: 50,
      },
      container: {
        flex: 1,
        alignItems: "center",
        justifyContent: "center",
        width: "100%",
        //margin: 10, //add this to break
      },
    });

Expo snack to run and modify the code: https://snack.expo.dev/@tomwaitforitmy/tab-menu

Do I need position: "absolute" and/or Dimensions.get("window").width? The layout breaks if you wrap it with margin or padding. In other words: Is my index-based positioning required? I would prefer to have relative alignments to use the menu wherever I want. However, I am fairly unexperienced with react-native-reanimated and I don't know if that's a decent idea or just foolish wish. Thank you for any feedback!

1

There are 1 best solutions below

1
On

PRO TIP: do not use Dimensions.get("window").width;

If your user will change from portrait to landscape, open his foldable or for some reason "change the dimension" of the screen, you will not react to that change. I needed to refactor all my app because of this :)

Move the logic inside your component and use: const { width } = useWindowDimensions();


Then, you came up with a pretty smart idea. I think there are no other ways rather than using positioning, but you can provide some fixes for margins, if you define them togheter with all your stuff

const numberOfTabs = 3;
const tabWith = windowWidth / numberOfTabs;
const MARGIN_VERTICAL_CONTAINER = 10;
const MARGIN_HORIZONTAL_BUTTON = 15;

...

const selectedButtonAnimatedStyle = useAnimatedStyle(() => {
  return {
    position: "absolute",
    // adjust here for MARGIN_HORIZONTAL_BUTTON
    left: position.value,
    right: 0,
    top: 0, 
    // adjust here for MARGIN_VERTICAL_CONTAINER
    width: tabWith,
  };
});

MAYBE, and I repeat, MAYBE you can achieve something animating flex property, for example making three buttons of flex: 1 and your "selection indicator" only for "just one flex, not all three". But this is only loud thinking.