I am creating a react native application and want to change the background color of an animated View after the user touches it. For more context, the view is in the shape of a square and I am rotating it 225 degrees when the component mounts. If the user touches the square, it will animate a flipping motion and show the other side of the square, which is a different color. The code I am using to do this can be seen below:
const app = (props) => {
let colors = [color1, color2, color3, ...];
let index = 0;
let animatedValue= new Animated.Value(0);
let squareSpin = new Animated.Value(0);
let val = 0;
useEffect(() => {
Animated.timing(squareSpin, {
toValue: 1,
duration: 3000,
easing: Easing.linear,
}).start();
}, []);
const startSpin = squareSpin.interpolate({
inputRange: [0, 1],
outputRange: ["0deg", "225deg"],
});
animatedValue.addListener(({ value }) => {
val = value;
});
let frontInt= animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ["0deg", "180deg"],
});
let backInt = animatedValue.interpolate({
inputRange: [0, 180],
outputRange: ["180deg", "360deg"],
});
let opacityFront = animatedValue.interpolate({
inputRange: [89, 90],
outputRange: [1, 0],
});
let opacityBack = animatedValue.interpolate({
inputRange: [89, 90],
outputRange: [0, 1],
});
const flip= () => {
if (val>= 90) {
Animated.spring(animatedValue, {
toValue: 0,
friction: 6,
tension: 8,
}).start();
} else {
Animated.spring(animatedValue, {
toValue: 180,
friction: 6,
tension: 8,
}).start();
}
};
const frontAnimated = {
transform: [{ rotateY: frontInt }],
};
const backAnimated = {
transform: [{ rotateY: backInt}],
};
return (
<Animated.View
style={{transform: [{ rotate: startSpin }] }}
>
<TouchableWithoutFeedback
onPress={() => {
index++;
flip();
}}>
<Animated.View
style={[
styles.shape,
{
backgroundColor:
colors[index % colors.length],
},
frontAnimated ,
{ opacity: opacityFront },
]}
>
</Animated.View>
<Animated.View
style={[
styles.shape,
{
backgroundColor:
colors[(index + 1) % colors.length],
},
{ position: "absolute" },
backAnimated ,
{ opacity: opacityBack },
]}
></Animated.View>
</TouchableWithoutFeedback>
<Animated.View>
)
}
The Problem: The animations all work great, but the issue is that each side of the square I am flipping can only take on one color. Notice how in the colors array, there are multiple colors that the square should be based on the number of times the user presses the square. However, this is not happening and each side of the square is always the color is started out to be (color1 for the top side of the square and color2 for the bottom side of the square). I think this is happening because the view does not realize that the index is changing because it is never rendered again. Or maybe it simply cannot chance its color due to some properties of Animated.View, I am not really sure. I tried forcing a render when the square is pressed using useState but that resulted in the square to undo its rotation that happened when the component was mounted, which I do not want to happen. How do I get the background color of the views to change based on the number of taps by the user?
Thanks!
I was able to fix this by using two different color interpolation values.
let colorSideOne = colorValue1.interpolate({
inputRange: [...Array(colors.length)].map((_, index) => index),
outputRange: colors,
});
let colorSideTwo = colorValue2.interpolate({
inputRange: [...Array(colors.length)].map((_, index) => index),
outputRange: colors,
});
and with these values set color to the background of the card
<Animated.View
style={[
styles.shape,
{
backgroundColor: colorSideOne,
},
frontAnimated,
{ opacity: opacityFront },
]}>
</Animated.View>
<Animated.View
style={[
styles.shape,
{
backgroundColor: colorSideTwo,
},
frontAnimated,
{ opacity: opacityFront },
]}>
</Animated.View>
You now need to just properly update the colorValues depending on the index.
Note that you need to do this alternately for the front and back values
<TouchableWithoutFeedback
style={{
borderWidth: 2,
borderColor: 'red',
}}
onPress={() => {
index++;
//side = side === 'front' ? 'back' : 'front';
//console.log('side',side);
// console.log('index',index, 'color length', colors.length);
if (index & 1) {
colorValue1.setValue((index + 1) % colors.length);
} else {
colorValue2.setValue((index + 1) % colors.length);
}
// console.log('color value', colorValue1, colorValue2);
flip();
}}>
....
....
For the clarity iam attaching this expo demo
Hope it is what you are expecting !
Inside render method
const BackgroundColorConfig = this.Animation.interpolate(
{
inputRange: [ 0, 0.2, 0.4, 0.6, 0.8, 1 ],
outputRange: [ '#f6f6f6', '#f6f6f6', '#f6f6f6', LIGHT_RED_COLOR, LIGHT_RED_COLOR, LIGHT_RED_COLOR ]
});
usage backgroundColor:BackgroundColorConfig
give above attribute to your component(it should be under Animated.View tag)
Make a function to call Animation and use it either on componentDidMount or on button click
Define this.Animated = new Animated.value(0)
in constructor
StartBackgroundColorAnimation = (value) =>
{
Animated.timing(
this.Animation,
{
toValue: value,
duration: 600
}
).start();
}
I have a 3 second opacity reveal over a loading image.
The animation starts with onLoadStart. onLoadEnd I'd like to quickly finish the animation regardless of the duration remaining.
i.e. - If there's 2 seconds left in the animation and the image loads I'd like finish the animation in 200ms. How?
Here's the code so far. Thanks.
import React from 'react';
import { Animated, ImageBackground } from 'react-native';
const ProgressiveImage = ({ source }) => {
const opacity = new Animated.Value(0);
const startOpacityAnimation = () =>
Animated.timing(opacity, {
toValue: 1,
duration: 3000,
}).start();
const endOpacityAnimation = () => {
// Take the current opacity value and
// finish the animation in 200ms.
};
const animatedOpacity = {
backgroundColor: opacity.interpolate({
inputRange: [0, 1],
outputRange: ['rgba(255, 255, 255, 0.4)', 'rgba(255, 255, 255, 0)'],
}),
};
return (
<ImageBackground
style={{ height: 100, width: 100 }}
source={source}
onLoadStart={startOpacityAnimation}
onLoadEnd={endOpacityAnimation}
>
<Animated.View style={{ flex: 1, ...animatedOpacity }} />
</ImageBackground>
);
}
You can try with:
const endOpacityAnimation = () => {
opacity.stopAnimation()
Animated.timing(opacity, {
toValue: 1,
duration: 200
}).start()
};
Or, if you want the current value of opacity in the stop animation:
opacity.stopAnimation((value) => {
if (value < 'some number which means only 1 seconds have passed') {
Animated.timing(opacity, {
toValue: 1,
duration: 200,
}).start();
} else {
// something if have less than 2 seconds to end the animation
}
});
I'm animating the TextInput to move with translateY, but when I apply the React Native animated, TextInput responds to typing only one character at a time. What I mean is, once a character has been input, the keyboard is dismissed.
I have three animations happening and others are working fine:
const [textInputAnimation] = useState(new Animated.Value(0))
useEffect(() => {
Animated.parallel([
Animated.timing(animation, {
toValue: 1,
duration: 800,
useNativeDriver: true,
}),
Animated.timing(textInputAnimation, {
toValue: 1,
duration: 400,
delay: 700,
useNativeDriver: true,
}),
Animated.timing(logoAnimation, {
toValue: 1,
duration: 400,
delay: 900,
useNativeDriver: true,
})
]).start()
}, [])
const translateYInterpolate = textInputAnimation.interpolate({
inputRange: [0, 1],
outputRange: [-50, 0]
})
const animatedOpacity = textInputAnimation.interpolate({
inputRange: [0, 1],
outputRange: [0, 1]
})
const animatedTranslateStyle = {
transform: [
{
translateY: translateYInterpolate
}
],
opacity: animatedOpacity,
}
const AnimatedTextInput = Animated.createAnimatedComponent(TextInput);
The animated TextInput:
<AnimatedTextInput
style={[styles.textInput, animatedTranslateStyle]}
onChangeText={text => onChangeText(text)}
value={value}
placeholder="Search for an item"
enablesReturnKeyAutomatically
clearButtonMode={'always'}
returnKeyType="search"
selectionColor="red"
/>
I used the animation on the react-native expo project.
I was going to rotate and change the opacity to my component(View) whenever the props are changed.
But I could not reproduce this animation.
Even if I remove the rotate animation, It doesn't work for the opacity animation.
This is my error screen.
And this is my some code.
...
let rotateValue = new Animated.Value(0);
let fadeValue = new Animated.Value(1);
const animationStart=()=>{
return Animated.parallel([
Animated.timing(rotateValue, {
toValue: 1,
duration: 1000,
useNativeDriver: true
}),
Animated.timing(fadeValue, {
toValue: 0,
duration: 1000,
useNativeDriver: true
})
]).start();
};
React.useEffect(()=> {
animationStart();
}, [spinInfoData]);
.....
<Animated.View style={{
transform: [
{
rotateY: rotateValue.interpolate({
inputRange: [0, 1],
outputRange: [6, 0]
})
}
],
opacity: fadeValue,
display: "flex",
justifyContent: "center",
height: hp(spinSize),
flexDirection: "row",
marginTop: hp(spinSize / -6)
}}>
.......
You can fix the bug about the red screen like this.
transform: [
{
rotateY: rotateValue.interpolate({
inputRange: [0, 1],
outputRange: ['180deg', '0deg']
})
}
],
And please change your code for the reset the animation when props is changed like this.
const rotateValue = new useRef(new Animated.Value(0)).current;
const saveRotateValue = rotateValue.interpolate({
inputRange: [0, 1],
outputRange: ['180deg', '0deg']
});
....
// change the props
React.useEffect(()=> {
fadeValue.setValue(1); // reset the fade animation
rotateValue.setValue(0); // reset the rotate animation
Animated.parallel([
Animated.timing(rotateValue, {
toValue: 1,
duration: 1000,
useNativeDriver: true
}),
Animated.timing(fadeValue, {
toValue: 0,
duration: 1000,
useNativeDriver: true
})
]).start();
}, [spinInfoData]);
.......
<Animated.View style={{
transform: [
{
rotateY: saveRotateValue
}
],
opacity: saveOpacity,
......
I want to shake the below View when my password is wrong.
for example:
it should be translateX from place 10, to place 20 for 4 time and 1 second.
then should be stopped in place 10.
place 10 (I mean the X position of View)
startShake = () => {
Animated.loop(
Animated.sequence([
Animated.timing(this.animatedValue, {toValue: 1, duration: 150, easing: Easing.linear, useNativeDriver: true}),
Animated.timing(this.animatedValue, {toValue: -1, duration: 300, easing: Easing.linear, useNativeDriver: true}),
Animated.timing(this.animatedValue, {toValue: 0, duration: 150, easing: Easing.linear, useNativeDriver: true}),
])
).start();
}
<Animated.View style={{transform: [{
translateX: this.animatedValue.interpolate({
inputRange: [0, 0],
outputRange: [0, 0]
})
}]
}}>
</Animated.View>
Thank you for all answers.
I just solved editing my code with the following code
constructor(props) {
super(props)
this.shakeAnimation = new Animated.Value(0);
}
startShake = () => {
Animated.sequence([
Animated.timing(this.shakeAnimation, { toValue: 10, duration: 100, useNativeDriver: true }),
Animated.timing(this.shakeAnimation, { toValue: -10, duration: 100, useNativeDriver: true }),
Animated.timing(this.shakeAnimation, { toValue: 10, duration: 100, useNativeDriver: true }),
Animated.timing(this.shakeAnimation, { toValue: 0, duration: 100, useNativeDriver: true })
]).start();
}
<Animated.View style={{ transform: [{translateX: this.shakeAnimation}] }}>
</Animated.View>
Here is the shake animation for Image component in react native, you can check it-
const bgImage = require('./components/images/ex.jpg')
class App extends Component {
constructor(props) {
super(props)
this.animatedValue = new Animated.Value(0)
}
handleAnimation = () => {
// A loop is needed for continuous animation
Animated.loop(
// Animation consists of a sequence of steps
Animated.sequence([
// start rotation in one direction (only half the time is needed)
Animated.timing(this.animatedValue, {toValue: 1.0, duration: 150, easing: Easing.linear, useNativeDriver: true}),
// rotate in other direction, to minimum value (= twice the duration of above)
Animated.timing(this.animatedValue, {toValue: -1.0, duration: 300, easing: Easing.linear, useNativeDriver: true}),
// return to begin position
Animated.timing(this.animatedValue, {toValue: 0.0, duration: 150, easing: Easing.linear, useNativeDriver: true})
])
).start();
}
}
To add this Animation to Image Component-
<Animated.Image
source={bgImage}
resizeMode='contain'
style={{
transform: [{
rotate: this.animatedValue.interpolate({
inputRange: [-1, 1],
outputRange: ['-0.1rad', '0.1rad']
})
}]
}}
/>
There is a library : react-native-animitable
You can do wonders using this library and is really very easy to use with least codes.
You can do this without loop.
startShake = () => {
this.animatedValue.setValue(0);
Animated.timing(this.animatedValue,
{
toValue: 1,
duration: 150,
easing: Easing.linear,
useNativeDriver: true
}
).start()
}
<Animated.View style={{transform: [{
translateX: this.animatedValue.interpolate({
inputRange: [0, 0.25, 0.50, 0.75, 1],
outputRange: [10, 20, 10, 20, 10]
})
}]
}}>
</Animated.View>
It might some help you to get your required animation
class App extends Component {
constructor(props) {
super(props)
this.animatedValue = new Animated.Value(0)
}
handleAnimation = () => {
// A loop is needed for continuous animation
Animated.loop(
// Animation consists of a sequence of steps
Animated.sequence([
// start rotation in one direction (only half the time is needed)
Animated.timing(this.animatedValue, {toValue: 1.0, duration: 150, easing: Easing.linear, useNativeDriver: true}),
// rotate in other direction, to minimum value (= twice the duration of above)
Animated.timing(this.animatedValue, {toValue: -1.0, duration: 300, easing: Easing.linear, useNativeDriver: true}),
// return to begin position
Animated.timing(this.animatedValue, {toValue: 0.0, duration: 150, easing: Easing.linear, useNativeDriver: true})
])
).start();
}
}
<Animated.View
style={{
transform: [{
rotate: this.animatedValue.interpolate({
inputRange: [-1, 1],
outputRange: ['-0.1rad', '0.1rad']
})
}]
}}
/>
It can be a good solution with two axes:
import React, { useState } from "react";
import { StyleSheet, View, Animated, TouchableWithoutFeedback, Easing } from "react-native";
const Shake = () => {
const [animation] = useState(new Animated.Value(0));
const startAnimation = () => {
animation.setValue(0);
Animated.timing(animation, {
toValue: 1,
duration: 1500,
easing: Easing.linear,
useNativeDriver: true
}).start(()=>{
animation.setValue(0)
});
}
const animatedStyles = {
transform: [
{
translateX: animation.interpolate({
inputRange: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],
outputRange: [0, 10, -10, 10, -10, 0, 0, 0, 0, 0, 0]
})
},
{
translateY: animation.interpolate({
inputRange: [0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1],
outputRange: [0, 0, 0, 0, 0, 0, 10, -10, 10, -10, 0]
})
}
]
}
return <View style={styles.container}>
<TouchableWithoutFeedback onPress={startAnimation}>
<Animated.View style={[styles.box, animatedStyles]} />
</TouchableWithoutFeedback>
</View>
}
const styles = StyleSheet.create({
container: {
flex: 1,
height: 600,
alignItems: "center",
justifyContent: "center",
},
box: {
backgroundColor: "tomato",
width:150,
height:150
}
});
export default Shake
// #flow
import React, { useCallback, useEffect, useRef } from 'react'
import { Image, Animated } from 'react-native'
// Constants
const DEFAULT_STEP_TIME = 1000
interface Props {
source: string | number;
intensity?: number;
stepTime?: number;
iterations?: number;
containerStyle?: any;
style?: any;
}
const ShakingIcon = ({
source,
intensity = 2,
stepTime,
iterations = 2,
containerStyle,
style,
}: Props) => {
const interval = useRef(null)
const animation = useRef(new Animated.Value(0))
const shakeAnimation = useCallback(() => {
Animated.loop(
Animated.sequence([
Animated.timing(animation.current, {
toValue: -intensity,
duration: 50,
useNativeDriver: true,
}),
Animated.timing(animation.current, {
toValue: intensity,
duration: 50,
useNativeDriver: true,
}),
Animated.timing(animation.current, {
toValue: 0,
duration: 50,
useNativeDriver: true,
}),
]),
{ iterations },
).start()
}, [])
const runAnimation = useCallback(async () => {
interval.current = setInterval(
() => shakeAnimation(),
stepTime || DEFAULT_STEP_TIME,
)
}, [])
useEffect(() => {
runAnimation()
return () => clearInterval(interval.current)
}, [])
return (
<Animated.View
style={[
containerStyle,
{ transform: [{ translateX: animation.current }] },
]}
>
<Image source={source} style={style} />
</Animated.View>
)
}
export default ShakingIcon
based on https://snack.expo.dev/embedded/#ananthu.kanive/shake?iframeId=t099o2svbu&preview=true&platform=web&theme=light