Scale an image with Animated react native expo is not applying permanently

1.2k views Asked by At

I’m trying to increase the size of an image on user press and decrease it when he presses again with animated API using the following:

const [viewState, setViewState]= useState(true);
const scaleAnim = (new Animated.Value(.9))




const scaleOut = () => {

if(viewState){
  Animated.timing(scaleAnim, {
    toValue: 2.2,
    duration: 2000,
    useNativeDriver:true,
  }).start(()=>{setViewState(false)});
}
else{
  Animated.timing(scaleAnim, {
    toValue: .9,
    duration: 700,
    useNativeDriver:true,
  }).start(setViewState(true));
}

 };

    <Animated.View style={{transform:[{scale:scaleAnim}]}} >
      <Image style={styles.image} source={require('../path..')} />
    </Animated.View>


const styles = StyleSheet.create({
  image: {
    width:70,
    resizeMode:"contain",
    height: 45,
    alignSelf: "center",
  },

But the issue is, whenever the duration is over, the size is going back to default. I want to to stay permanently and do the opposite when the user presses again(decrease size)

Any suggestions?

2

There are 2 answers

0
Ashwith Saldanha On BEST ANSWER

Created a Component hope this is how you wanted....

snack: https://snack.expo.io/neEtc2ihJ


export default function App() {
  const [viewState, setViewState] = React.useState(true);
  const scale = React.useRef(new Animated.Value(1)).current;
  const [init, setInit] = React.useState(true);
  React.useEffect(() => {
    if (init) {
      setInit(false);
    } else {
      if (viewState) {
        Animated.timing(scale, {
          toValue: 2,
          duration: 1000,
          useNativeDriver: true,
        }).start();
      } else {
        Animated.timing(scale, {
          toValue: 0.5,
          duration: 700,
          useNativeDriver: true,
        }).start();
      }
    }
  }, [viewState]);

  const scaleOut = () => {
    setViewState(!viewState);
  };

  return (
    <View style={styles.container}>
      <Animated.View style={{ transform: [{ scale }] }}>
        <Image
          style={styles.image}
          source={require('./assets/snack-icon.png')}
        />
      </Animated.View>
      <Button title="animate" onPress={scaleOut} />
    </View>
  );
}
0
PhantomSpooks On

Firstly you want your animated value to either useState or useRef. The react-native example uses useRef, so I'd suggest you to do the same. I'd also suggest tha you use an interpolation to scale so that you can tie more animations to that one animated value. The result would be something like this:

    const animatedValue = useRef(new Animated.Value(0)).current;   
    const [ toggle, setToggle ] = useState(false)
    const scaleOut = () => {
      let animation
      if(!toggle){
        animation = Animated.timing(animatedValue, {
          toValue: 1,
          duration: 700,
          useNativeDriver:true,
        });
      }
      else{
        animation = Animated.timing(animatedValue, {
          toValue: 0,
          duration: 2000,
          useNativeDriver:true,
        });
      }
      animation.start(()=>{
        setToggle(!toggle)
      })
    };
    let scaleAnim = animatedValue.interpolate({
      inputRange:[0,1],
      outputRange:[0.9,2.2]
    })
    return (
      <Animated.View style={{transform:[{scale:scaleAnim}]}} >
        <TouchableOpacity onPress={scaleOut}>
          <Image style={styles.image} source={require('../path..')} />
        </TouchableOpacity>
      </Animated.View>
    );

By doing this, you can scale multiple images at whatever size you want by just adding another interpolation. But if you have no desire to do that:

    const scaleOut = () => {
      let animation
      if(!toggle){
        animation = Animated.timing(animatedValue, {
          toValue: 2.2,
          duration: 2000,
          useNativeDriver:true,
        });
      }
      else{
        animation = Animated.timing(animatedValue, {
          toValue: 0.9,
          duration: 700,
          useNativeDriver:true,
        });
      }
      animation.start(()=>{
        setToggle(!toggle)
      })
    };
    return (
      <Animated.View style={{transform:[{scale:animatedValue}]}} >
        <TouchableOpacity onPress={scaleOut} />
          <Image style={styles.image} source={require('../path..')} />
        </TouchableOpacity>
      </Animated.View>
    );

If you want to go a step further, swap out the TouchableOpacity for a Pressable, put the animations in a Animated.loop and start that in onPressIn, and on pressOut stop the animations and bring the set the animatedValue back to initial value:

    const onPressIn= ()=>{
      Animated.loop([
        Animated.timing(animatedValue, {
          toValue: 2.2,
          duration: 2000,
          useNativeDriver:true,
        }),
        Animated.timing(animatedValue, {
          toValue: 0.9,
          duration: 700,
          useNativeDriver:true,
        });
      ],{useNativeDriver:true}).start()
    }
    const onPressOut= ()=>{
      animatedValue.stop()
      Animated.timing(animatedValue,{
          toValue: 0.9,
          duration: 700,
          useNativeDriver:true,
      })
    }
    return(
      <Pressable onPressIn={onPressIn} onPressOut={onPressOut}>
        <Animated.View style={{transform:[{scale:animatedValue}]}} >
            <Image style={styles.image} source={require('../path..')} />
        </Animated.View>
      </Pressable>
    );