Home > Net >  Random Animation within map function - Expo - Reanimated2
Random Animation within map function - Expo - Reanimated2

Time:12-07

I have a screen with four animated Views. They are rendered by a map function. When the screen is mounted a scale animation is triggered for once. After that, i'm trying to achieve to randomly animate (scale simple animation) only one View every 4 seconds. I can't find the way to get the reference of each View and only animate one randomly. Here is my code:

import { useRef, useEffect } from 'react'
import { View, StyleSheet, TouchableOpacity } from 'react-native'
import Animated, { useAnimatedStyle, useSharedValue, withTiming, withSpring, withRepeat } from 'react-native-reanimated'

const SIZE = 65.0

const RandomZoomInScreen = ({ navigation }) => {

  const scale = useSharedValue(0)
  const bounce = useSharedValue(1)
  const itemEls = useRef(new Array())

  const reanimatedStyle = useAnimatedStyle(() => {
    return {
      transform: [{ scale: scale.value }]
    }
  })

  const bounceStyle = useAnimatedStyle(() => {
    return {
      transform: [{ scale: bounce.value }]
    }
  })

  useEffect(() => {
    scale.value = withSpring(1, {stiffness:200})
    return () => {
      scale.value = 0
    }
  }, [])

  useEffect(() => {
    const intervalId = setInterval(() => {  //assign interval to a variable to clear it.
     'worklet'
      bounce.value = withRepeat(withTiming(1.3),2,true)
    }, 4000)
  
    return () => clearInterval(intervalId)
  }, [])

  return (
    <View style={styles.container}>
      {[1, 2, 3, 4].map((square, index) => {
        return <TouchableOpacity
            activeOpacity={0.7}
            key={index}
          >
          <Animated.View 
          
          ref={(element) => itemEls.current.push(element)} style={[styles.square, reanimatedStyle, bounceStyle]} />
        </TouchableOpacity>
      })}

    </View>
  )

}

export default RandomZoomInScreen

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'space-evenly',
    alignItems: 'center'
  },
  square: {
    width: SIZE * 2,
    height: SIZE,
    backgroundColor: 'green',
  }
})

CodePudding user response:

You want to animate a single box every 4 seconds, right?

If you want to animate a single box, I think the best way is to create individual shared values for each box, by either creating a Box component, or creating an array of shared values (or something like that), so that you can change each value independently.

Here I refactored your code, creating a Box component, and creating a scale shared value inside the component:

import { useEffect, useState } from "react";
import { View, StyleSheet, TouchableOpacity } from "react-native";
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  withSpring,
  withSequence,
  withTiming,
} from "react-native-reanimated";

const SIZE = 65.0;

const Box = ({ shouldAnimate }) => {
  const scale = useSharedValue(0);
  const reanimatedStyle = useAnimatedStyle(() => {
    return {
      transform: [{ scale: scale.value }],
    };
  });

  useEffect(() => {
    scale.value = withSpring(1, { stiffness: 200 });
    return () => {
      scale.value = 0;
    };
  }, []);

  useEffect(() => {
    if (shouldAnimate) {
      scale.value = withSequence(withTiming(1.3), withTiming(1));
    }
  }, [shouldAnimate]);

  return (
    <TouchableOpacity activeOpacity={0.7}>
      <Animated.View style={[styles.square, reanimatedStyle]} />
    </TouchableOpacity>
  );
};

const RandomZoomInScreen = ({ navigation }) => {
  const [selectedBox, setSelectedBox] = useState(-1);

  useEffect(() => {
    const intervalId = setInterval(() => {
      // you want to select a number between 0 and 3 (the indeces of the boxes) every 4 seconds
      const nextBox = Math.floor(Math.random() * 4);

      // to garantee the new value will always be different from previous one...
      // we can sum 1 and apply a mod 4 (so the result is always between 0 and 3)
      setSelectedBox((previousBox) =>
        previousBox === nextBox ? (nextBox   1) % 4 : nextBox
      );
    }, 4000);

    return () => clearInterval(intervalId);
  }, []);

  return (
    <View style={styles.container}>
      {[1, 2, 3, 4].map((square, index) => {
        // we should animate when the selected box is equal to the index
        return <Box key={square} shouldAnimate={index === selectedBox} />;
      })}
    </View>
  );
};

export default RandomZoomInScreen;

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: "space-evenly",
    alignItems: "center",
  },
  square: {
    width: SIZE * 2,
    height: SIZE,
    backgroundColor: "green",
  },
});

Keep in mind that there are probably other approaches to achieving this.

  • Related