Home > OS >  React Native Reanimated 2 (Animate SVG based on route changed)
React Native Reanimated 2 (Animate SVG based on route changed)

Time:01-01

I have a react functional component Svg being used as an icon for the Bottom-TabBar. On route change the current state.index is compared to the route index. The result which essentially is a boolean state isFocused is passed to the Svg.

I am trying to animate the Svg based on this state and can not get the simple operation completed using reanimated. I am most certain that the value of fill is not being updated in useAnimatedProps hook but I lack the experience of having deep knowledge with reanimated. Any help will be greatly appreciated

import Animated, {
  useAnimatedProps,
  useSharedValue,
} from 'react-native-reanimated';
import Svg, { Circle, Path } from 'react-native-svg';

const AnimatedSvg = Animated.createAnimatedComponent(Svg);

export default ({ isFocused }) => {
  const fill = useSharedValue({ fill: 'transparent' });
  const animatedProps = useAnimatedProps(() => {
    isFocused
      ? (fill.value = { fill: 'red' })
      : (fill.value = { fill: 'transparent' });

    return fill.value;
  });
  return (
    <AnimatedSvg
      xmlns="http://www.w3.org/2000/svg"
      width={24}
      height={24}
      animatedProps={animatedProps}
      stroke={'white'}
      strokeWidth={2}
      strokeLinecap="round"
      strokeLinejoin="round"
      className="feather feather-search">
      <Circle cx={11} cy={11} r={8} />
      <Path d="m21 21-4.35-4.35" />
    </AnimatedSvg>
  );
};

CodePudding user response:

A more common approach would be to use a "progress-variable" as the shared value.

const fillProgress = useSharedValue(isFocused? 1 : 0);

You would the use this progress-variable to generate the animatedProps. Please note the use of interpolateColor to get the actual interpolated color.

const animatedProps = useAnimatedProps(() => {
    const fillValue = interpolateColor(fillProgress.value, [0, 1], ["transparent", "red"]);  
    return {
        fill: fillValue
    }
});

You have to return an object with the properties you would want to animate. If you wanted to animate fill and opacity for example, you would return {fill: "", opacity: -1} with the appropriate values instead of "" and -1. Lastly you have to make the actual element you want to animate Animated. In this case, you want to animate the Circle, not the Svg so that has to be an Animated object.

const AnimatedCircle = Animated.createAnimatedComponent(Circle);

You can then detect being focused using useEffect and animate accordingly.

useEffect(() => {
    fillProgress.value = withTiming(isFocused? 1 : 0);
}, [isFocused]);

Remember to set the initial value for the fillProgress just like you do in the withTiming function.

To summarize, you have to animate the element, that uses the animated properties and you should use progress-variables as mentioned above.

Here is the full modified code (tested on Android):

import Animated, {
    useAnimatedProps,
    useSharedValue,
} from 'react-native-reanimated';
import Svg, { Circle, Path } from 'react-native-svg';

const AnimatedCircle = Animated.createAnimatedComponent(Circle);

export default function Icon ({ isFocused }) {
    const fillProgress = useSharedValue(isFocused? 1 : 0);
    const animatedProps = useAnimatedProps(() => {
        const fillValue = interpolateColor(fillProgress.value, [0, 1], ["transparent", "red"]);  
        return {
            fill: fillValue
        }
    });


    useEffect(() => {
        fillProgress.value = withTiming(isFocused? 1 : 0);
    }, [isFocused]);

    return (
      <Svg
        width={24}
        height={24}
        stroke={'white'}
        strokeWidth={2}
        strokeLinecap="round"
        strokeLinejoin="round"
        className="feather feather-search">
            <AnimatedCircle animatedProps={animatedProps} cx={11} cy={11} r={8} />
            <Path d="m21 21-4.35-4.35" />
      </Svg>
    );
};
  • Related