4

I need to create an animation with an image that will circle around another image. I have already tried to use suggestions from a similar question like Animate a Circle around another circle , but unfortunately it didn't help. I tried looking into 3rd party modules that would offer the desirable functionality, but haven't found something that would fit my need.

I found a helpful article to understand the circular motion in JavaScript, however I have a hard time to replicate it in React Native animation. I believe I simply have a hard time understanding a proper usage of Animated API and transform style properties when it comes to animating circular movement.


<View style={animationContainer}>
    <Image
      source={require('./images/image.png')}
      style={image}
    />
    <Animated.Image
      source={require('./images/icon.png')}
      style={circlingIcon}
    />
</View>
Evgenii Klepilin
  • 695
  • 1
  • 8
  • 21
  • 1
    [This](https://stackoverflow.com/questions/47911256/react-native-circle-transform-translate-animation) is what you are looking for – CampbellMG Feb 04 '20 at 04:56
  • @CampbellMG I have seen that one, and it looked like an OK solution, but not the best. But I appreciate pointing that example out – Evgenii Klepilin Feb 04 '20 at 07:59
  • What part of this solution doesn't work for you? – CampbellMG Feb 04 '20 at 08:00
  • What I don't like about this solution is that it is possible to approximate it down to the circular movement, but not to provide exact functional correlation. There is a variable `snapshot` which defines how many frames would you want to have in your animation. What I was hoping finding would be the proper interpolation of a value that would result in a continuous graph/movement – Evgenii Klepilin Feb 04 '20 at 08:03
  • 1
    The problem you are going to run into trying to create circular movement with the `Animated` API is that you only have access to basic mathematical functions (add, divide, modulo etc.). That is why this solution samples values along a circular path rather than an exact figure. You could try a library like [this](https://github.com/rastapasta/react-native-animated-math) that includes a few more complex functions like sin using a Taylor Series. – CampbellMG Feb 04 '20 at 08:14
  • I guess you're right. Maybe I'm asking a little too much from React Native :) – Evgenii Klepilin Feb 04 '20 at 08:27

2 Answers2

2

I have posted a similar butter smooth solution for the question react native circle transform translate animation

Full code:


import React, {Component} from 'react';
import {View, Text, Animated, StyleSheet, Easing} from 'react-native';

export default class Circle extends Component {
    constructor() {
        super();
        this.animated = new Animated.Value(0);
        var inputRange = [0, 1];
        var outputRange = ['0deg', '360deg'];
        this.rotate = this.animated.interpolate({inputRange, outputRange});
        outputRange = ['0deg', '-360deg'];
        this.rotateOpposit = this.animated.interpolate({inputRange, outputRange});
    }

    componentDidMount() {
        this.animate();
    }

    animate() {
      Animated.loop(
        Animated.timing(this.animated, {
            toValue: 1,
            duration: 4000,
            useNativeDriver: true,
            easing: Easing.linear,
        }),
      ).start();
    }
    render() {
        const transform = [{rotate: this.rotate}];
        const transform1 = [{rotate: this.rotateOpposit}];
        return (
          <View style={styles.container}>
            <Animated.View style={[styles.item, {transform}]}>
              <Animated.View style={[styles.topItem, {transform: transform1}]}>
                <Text style={styles.text}>Test</Text>
              </Animated.View>
            </Animated.View>
          </View>
        );
    }
 }
 const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
    },
    item: {
        position: 'absolute',
        width: 100,
        height: 200, // this is the diameter of circle
    },
    topItem: {
        width: '100%',
        height: 20,
        backgroundColor: 'red',
        position: 'absolute',
        alignItems: 'center',
        justifyContent: 'center',
    },
    text: {
        color: '#fff',
    },
 });

I hope it will help you..

Aswin C
  • 1,152
  • 7
  • 12
1

I have taken a solution from a question react native circle transform translate animation and refactored it a little by separating interpolation of coordinates over Y and X in two different functions.

interface InterpolatedMotion {
  translateX: Animated.Value;
  translateY: Animated.Value;
}

interface AnimatedIcon extends InterpolatedMotion {
  animated: Animated.Value;
}

interface State {
  icon: AnimatedIcon;
}

const defaultAnimatedIcon = (animatedValue: number): AnimatedIcon => ({
  animated: new Animated.Value(animatedValue),
  translateX: new Animated.Value(0),
  translateY: new Animated.Value(0),
});

export class Animation extends PureComponent<Props, State> {
  state = {
    icon: defaultAnimatedIcon(0),
  }

  constructor(props) {
    super(props);
    let { icon } = this.state;

    icon.animated.setValue(0);

    const snapshot = 50;
    const radius = 200;

    const inOutX = this.interpolateCircularMotionOverX(snapshot, radius);

    icon.translateX = coins.animated.interpolate(inOutX);

    const inOutY = this.interpolateCircularMotionOverY(snapshot, radius);

    icon.translateY = coins.animated.interpolate(inOutY);
  }

  componentWillMount(): void {
    this.startAnimation();
  }

  startAnimation = (): void => {
    let { icon } = this.state;

    icon.animated.setValue(0);

    let animations = [
      Animated.timing(
        icon.animated,
        {
          toValue: 1,
          duration: 8000,
          easing: Easing.linear,
        },
      ),
    ];

    Animated.loop(
      Animated.parallel(animations),
    ).start(() => {
      icon.animated.setValue(0);
    });
  }

  interpolateCircularMotionOverX = (snapshot: number, radius: number) => {
    const inputRange = [];
    const outputRange = [];
    for (let i = 0; i <= snapshot * 2; ++i) {
      const value = i / snapshot;
      const move = Math.sin(value * Math.PI * 2) * radius;
      inputRange.push(value);
      outputRange.push(move);
    }
    return { inputRange, outputRange };
  }

  interpolateCircularMotionOverY = (snapshot: number, radius: number) => {
    const inputRange = [];
    const outputRange = [];
    for (let i = 0; i <= snapshot * 2; ++i) {
      const value = i / snapshot;
      const move = -Math.cos(value * Math.PI * 2) * radius;
      inputRange.push(value);
      outputRange.push(move);
    }
    return { inputRange, outputRange };
  }

  render(): JSX.Element {
    const { icon } = this.state;

    const transformIcon = [
      { translateY: icon.translateY },
      { translateX: icon.translateX },
    ];

    return (
        <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
          <View style={{ flex: 1 }}>
            <Animated.Image
              source={require('./images/coins.png')}
              style={[Styles.forms.circlingIcon, { transform: transformCoins }]}
            />
          </View>
        </View>
    );
}

This has helped me by providing a flexible and reusable solution for any future instances of circular animation.

Evgenii Klepilin
  • 695
  • 1
  • 8
  • 21
  • 1
    I used `Animated.Image` and it works locally (development mode) but after building and uploading to testflight, the images became squares. Do you have any idea by chance? – ruin3936 Nov 30 '20 at 08:46
  • Have you tried using useNativeDriver : https://reactnative.dev/blog/2017/02/14/using-native-driver-for-animated ? I'm not sure if it will help you, but worth a try. – Evgenii Klepilin Dec 01 '20 at 02:38
  • yup and it’s still the same. What I did is I changed the `Animated.Image` to `Animated.View` and passed the image to `Image` component and it works. – ruin3936 Dec 02 '20 at 03:16