Search code examples
react-nativeanimationreact-native-svg

What's the equivalent of react-native-svg rotation with origins using react-native animated api


I'm having below svg path, which rotates around origin in every value state change successfully, where value is some degrees value:

<Path
   transform={{
      rotation: this.state.value,
      originX: width / 2,
      originY: height / 2
   }}
   d={`M ${width * 0.5} ${height * 0.5} L${width * 0.75} ${height * 0.75}`} //the actual path does not matter 
   fill={Colors.black}
   stroke={Colors.black}
   />

I call setState on the value every ~20 ms, but this is not smooth enough, + it's not the recommended way of doing a transition. What i want to achieve, is animate the rotation using the animated API, to achieve much smoother rotation.

I have tried making my value state an Animated.Value. Then making the Path a Animated.createAnimatedComponent(Path) and calling:

Animated.timing(this.state.value, {
    toValue: newDegreesValue,
    duration: 1000,
    useNativeDriver: true
}).start();

Then i render the path like this:

<Path
   style={transform:[{
      rotate: this.state.value
   }]}
   d={`M ${width * 0.5} ${height * 0.5} L${width * 0.75} ${height * 0.75}`} //the actual path does not matter 
   fill={Colors.black}
   stroke={Colors.black}
   />

This doesn't work at all close to the previously working code using state. One reason is the originX, originY values that are not supported by the animation API and i don't know how to replace, but I'm not sure it's the only reason, maybe rotate is somehow different than rotation property too?. So, the question is, how can the same outcome by the code which is continuously calling setState be achieved using animated api?


Solution

  • This is the way i achieved it:

    // init the animatedComponents
    const NeedlePath = Animated.createAnimatedComponent(Path);
    const AnimatedG = Animated.createAnimatedComponent(G);
    
    // set the animation
    Animated.timing(this.state.angle, {
            toValue: 240, // degrees
            duration: 1000,
            useNativeDriver: true
        }).start();
    
    // pivotX and pivotY are the originX, originY points.
    // the width and height are calculated dynamically, so that the whole svg is a square (equal height width), and the center of it are my origins.
    render() {
        let height = this.state.height;
        let width = this.state.width;
        let [pivotX, pivotY] = [0, 0];
        if (height && width) {
            [pivotX, pivotY] = [width / 2, height / 2];
        }
       return (
            <View
                style={{ flex: 1}}
                onLayout={event => {
                    this.findDimesions(event.nativeEvent.layout); // find height and width dynamically, so that this remain a square container with same height and width
                }}
            >
                {height !== undefined && width !== undefined && (
                    <Svg height={height} width={width} style={{ alignItems: "center" }}>
                       <G transform={`translate(${pivotX}, ${pivotY})`}>
                            <AnimatedG
                                style={{
                                    transform: [
                                        {
                                            rotate: this.state.angle.interpolate({
                                                inputRange: [0, 360],
                                                outputRange: [`0deg`, `360deg`]
                                            })
                                        }
                                    ]
                                }}
                            >
                                <NeedlePath
                                    transform={`translate(-${pivotX} -${pivotY})`}
                                    d={`M ${width * 0.5} ${height * 0.5} L${width * 0.5 + width * 0.25} ${height * 0.5 + height * 0.25}`}
                                    fill={Colors.black}
                                    stroke={Colors.black}
                                />
                            </AnimatedG>
                        </G>
                    </Svg>
                )}
            </View>
        );
    }
    

    The style={{ alignItems: "center" }} is crucial to achieve the same result on android too, instead of setting translationX and translationY with offsetAndroid. This is working for version v9.13.3 of react-native-svg. Maybe in newer versions this get affected, because of fixing the translations problem, and i don't know what the impact will be on the code here. You can refer here for more info about the translations with offset needed on android