6

I have created a simple animation with Animated from react-native with react-native-svg.

This do the jobs well,

But Now I switched to react-native-reanimated cuz I read on their website that reanimated is faster than Animated from react-native.

But here I faced with a problem, and that is I cant find function addListener to listen to the value changes.

Code with Animated from react-native:

const circleRadius = new Animated.value(100);

circleRadius.addListener( circleRadius => {
       circleSVG.current.setNativeProps({ cx: circleRadius.value.toString() });
});

How can I implement above addListener function in react-native-reanimated ?

Muhammad
  • 2,572
  • 4
  • 24
  • 46

3 Answers3

18

You can achieve similar behavior using Animated.call. Here is a nice tutorial about the subject.

Edited:

For example, to listen to circleRadius changes, you could use this code:

  import { call, useCode } from 'react-native-reanimated'

  useCode(() => {
    return call([circleRadius], (circleRadius) => {
      console.log(circleRadius)
    })
  }, [circleRadius])

Does it do what you want?

Vladyslav Zavalykhatko
  • 15,202
  • 8
  • 65
  • 100
  • didbt work as I want, please see once again my question – Muhammad Mar 05 '20 at 09:57
  • 1
    @Muhammad sorry to hear it. I'll add a minimal code example, maybe we don't understand each other :) – Vladyslav Zavalykhatko Mar 06 '20 at 10:43
  • Thank you very much, could you please let me know where the `call` function comes from ??? – Muhammad Mar 06 '20 at 12:17
  • Thank you very much, I am getting this error after updating as your code: Attempt to invoke virtual method 'double.java.lang.Double.doubleValue()' on a null object reference – Muhammad Mar 06 '20 at 13:38
  • Also I am updating only the `cx` like this `circleSVG.current.setNativeProps({ cx: circleRadius.value.toString() });` so in your code what will be put to `cx' ? – Muhammad Mar 06 '20 at 13:39
3
import React, { FC, useRef } from 'react';
import { StyleSheet, TextInput, View } from 'react-native';
import Svg, { G, Circle } from 'react-native-svg';
import Animated, { call, Easing, interpolate, useCode } from 'react-native-reanimated';
import { timing } from 'react-native-redash';

interface DonutChartProps {
  percentage: number;
  radius?: number;
  strokeWidth?: number;
  duration?: number;
  color?: string;
  delay?: number;
  textColor?: string;
  max?: number;
}

const AnimatedCircle = Animated.createAnimatedComponent(Circle);
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);

const DonutChart: FC<DonutChartProps> = ({
  percentage,
  radius = 40,
  strokeWidth = 10,
  duration = 500,
  color = 'tomato',
  textColor,
  max = 100,
}) => {
  const inputRef = useRef<TextInput>(null);

  const halfCircle = radius + strokeWidth;
  const circumference = 2 * Math.PI * radius;
  const maxPercentage = (100 * percentage) / max;

  const animation = timing({
    from: 0,
    to: 1,
    duration,
    easing: Easing.inOut(Easing.linear),
  });

  const strokeDashoffset = interpolate(animation, {
    inputRange: [0, 1],
    outputRange: [circumference, circumference - (maxPercentage * circumference) / 100],
  });

  const textValue = interpolate(animation, {
    inputRange: [0, 1],
    outputRange: [0, Math.round(percentage)],
  });

  useCode(
    () => [
      call([textValue], ([textValue]) => {
        if (inputRef.current) {
          inputRef.current.setNativeProps({
            text: `${Math.round(textValue)}`,
          });
        }
      }),
    ],
    [textValue]
  );

  return (
    <View>
      <Svg width={radius * 2} height={radius * 2} viewBox={`0 0 ${halfCircle * 2} ${halfCircle * 2}`}>
        <G rotation="-90" origin={`${halfCircle}, ${halfCircle}`}>
          <Circle
            cx="50%"
            cy="50%"
            stroke={color}
            strokeWidth={strokeWidth}
            r={radius}
            fill="transparent"
            strokeOpacity={0.2}
          />
          <AnimatedCircle
            cx="50%"
            cy="50%"
            stroke={color}
            strokeWidth={strokeWidth}
            r={radius}
            fill="transparent"
            strokeDasharray={circumference}
            strokeDashoffset={strokeDashoffset}
            strokeLinecap="round"
          />
        </G>
      </Svg>
      <AnimatedTextInput
        ref={inputRef}
        underlineColorAndroid="transparent"
        editable={false}
        defaultValue="0"
        style={[
          StyleSheet.absoluteFillObject,
          { fontSize: radius / 2, color: textColor ?? color, fontWeight: '900', textAlign: 'center' },
        ]}
      />
    </View>
  );
};

export default DonutChart;

user8573847
  • 51
  • 1
  • 5
1

Reanimated is meant to be a declarative API that allows you to run more advanced animations and thus complex logic on the native thread.

The reason something similar to addListener is not implemented is because it would require unnecessary messages between the native and JS thread. So rather than using a listener and setNativeProps to update the cx property of your circle, it would be best to use an AnimatedNode.

const circleRadius = new Animated.value(100);

circleRadius.addListener( circleRadius => {
       circleSVG.current.setNativeProps({ cx: circleRadius.value.toString() });
});

import { Circle } from 'react-native-svg';

// must make Circle compatible with Animated Values
const AnimatedCircle = Animated.createAnimatedComponent(Circle);

// then within your svg
<AnimatedCircle 
  // ... add other props
cx={circleRadius}
/>
Taylor Johnson
  • 1,845
  • 1
  • 18
  • 31
  • thank you, your code is same my code, so could you please show me whats your solution – Muhammad May 21 '20 at 23:39
  • Could you elaborate on your use case or add more of your code to the question? I'm not sure how or what is updating the AnimatedValue circleRadius – Taylor Johnson May 21 '20 at 23:51