Search code examples
javascriptreact-nativereact-native-reanimatedreact-native-reanimated-v2

Implement react navigation to an existing animated tabbar


I have a custom animated tabbar component with react-native-reanimated. Im trying to implement it with react navigation but when i do the animation doesnt work. Any help to what to add for it to be functional. Here are the components Tab component

interface TabProps {
  children: ReactElement;
  onPress: () => void;
  // navigation: NavigationStackScreenProps<any, any>;
  active: Animated.Node<number>;
  transition: Animated.Node<number>;
  index: number;
  label: string;
}

const styles = StyleSheet.create({
  icon: {
    overflow: 'hidden',
  },
});

export default ({
  children,
  active,
  transition,
  index,
  onPress,
}: TabProps) => {
  const isActive = eq(active, index);

  const activeTransition = withTransition(isActive, {duration: DURATION});
  const isGoingLeft = greaterThan(transition, active);
  const width = interpolateNode(activeTransition, {
    inputRange: [0, 1],
    outputRange: [0, ICON_SIZE],
  });
  const direction = cond(
    isActive,
    cond(isGoingLeft, 'rtl', 'ltr'),
    cond(isGoingLeft, 'ltr', 'rtl'),
  );

  return (
    <TouchableWithoutFeedback {...{onPress}}>
      <Animated.View
        style={{
          width: ICON_SIZE,
          height: ICON_SIZE,
          direction
        }}>
        <View style={StyleSheet.absoluteFill}>{children}</View>
        <Animated.View style={[styles.icon, {width}]}>
          {cloneElement(children, {active: true})}
        </Animated.View>
      </Animated.View>
    </TouchableWithoutFeedback>
  );
};

TabBar index:

const tabs = [
  {icon: <Home />, label: 'Home'},
  {icon: <User />, label: 'Video'},
  {icon: <Tv />, label: 'LiveStream'},
  {icon: <Wallet />, label: 'Signin'},
];
const styles = StyleSheet.create({
  container: {
    backgroundColor: '#F0F0F6',
    width: '90%',
    position: 'absolute',
    bottom: 20,
    left: '5%',
    borderRadius: 10,
  },
  tabs: {
    flexDirection: 'row',
    alignItems: 'center',
    justifyContent: 'space-between',
  },
  tab: {
    width: SEGMENT / 1.2,
    height: ICON_SIZE + PADDING * 2,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center',
  },
});

export default ({...props}) => {
  const active = new Value<number>(0);
  let navigation = useNavigation();
  const transition = withTransition(active, {duration: DURATION});
  const activeTransition = new Value(0);
  useCode(
    () =>
      block([
        onChange(active, set(activeTransition, 0)),
        set(activeTransition, timing({duration: DURATION})),
      ]),
    [active, activeTransition],
  );
  return (
    <SafeAreaView style={styles.container}>
      <View style={styles.tabs}>
        {tabs.map(({icon, label}, index) => (
          <View key={index} style={styles.tab}>
            <Weave {...{active, transition, index}} />
            <Tab
              onPress={() => {
                active.setValue(index);
                console.log(active);
                // title.setValue(label);
                props.navigation.navigate(label);
              }}
              {...{active, transition, index, label}}>
              {icon}
            </Tab>
          </View>
        ))}
        <Particules {...{transition, activeTransition}} />
      </View>
    </SafeAreaView>
  );
};

Here is the dribble example i was trying to copy https://dribbble.com/shots/5380015-WeChat-Tab-Bar-Redesign


Solution

  • What version of react-native-reanimated and react-native are you using? There's a few things that I'm seeing that should be causing issues, but I'm also not entirely sure what's available for your versions.

    The biggest thing I'm seeing is that you're instantiating new Value objects on every render of the component, which should be causing issues if trying to animate in a functional way. Looking into react-native-reanimated a little bit there appears to be a few hooks (useValue and useSharedValue) that should provide you with a consistent object that you can use where applicable. It looks like you'll want to use useValue.

    Since the animations are giving you troubles it may be a good idea to use the examples provided in their fundamentals as good reference for side-by-side comparison. This could be especially useful if you had/have it working as a class component to compare the differences. As well as their useAnimatedStyle hook for how they handle animating in a functional component.

    A few other items to note

    1. You may need to replace the timing call to be withTiming. timing appears to be specific for v1
    2. Rather than using set you may want to useSharedValue and just assign to the shared value's value property. set also appears to be specifically for v1 of reanimated
    // useSharedValue is provided by "react-native-reanimated"
    const active = useSharedValue(0);
    const activeTransition = useSharedValue(0);
    
    // useEffect is provided by "react"
    useEffect(() => {
      // Set activeTransition to 0
      activeTransition.value = 0;
    }, [active.value]); // When active.value changes
    

    Hopefully this helps