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();
}
Related
I try to do something like this but is error.
onPress a Btn the height of the Animated View change from HEIGHT1 (Value=0) to INTERPOLATE_1 (Value=1). And after this change I want when scrolling the ScrollView the height Value change to HEIGHT3.
const SCROLLING = useRef(new Animated.Value(0)).current;
const INTERPOLATE_1 = SCROLLING.interpolate({
inputRange: [0, 100],
outputRange:[ HEIGHT2 , HEIGHT3 ],
})
const HEIGHT_ANIM = useRef(new Animated.Value(0)).current
const INTERPOLATE_2 = HEIGHT_ANIM.interpolate({
inputRange: [0,1],
outputRange: [ HEIGHT1, INTERPOLATE_1 ],
})
<Animated.View style={{
height: INTERPOLATE_2
>
<Animated.ScrollView
scrollEventThrottle={16}
style={{flex:1}}
onScroll={Animated.event(
[{
nativeEvent: {
contentOffset: {
//
y: SCROLLING,
},
},
}],
{ useNativeDriver: false },
)}
</Animated.View>
HEIGHT IS AN EXAMPLE (I KNOW FOR HEIGHT OR WIDTH SPECIFICALLY CAN DO THIS WITH OTHER EASIER WAY BUT I WANT TO KNOW IF I CAN DO SOMETHING LIKE THIS ABOVE, SO PLEASE ANSWER ONLY MY QUESTION, NO OTHER WAY)
I want to create a shaking/floating (call it what you want) animation to an image.
Basically the image will float or wiggle left to right and at the same time up and down.
The animation will be triggered automatically.
This is the component I have. Image is a round ball so nothing fancy.
I tried putting values in inputRange and outputRange but nothing happened. Probably because it doesn't trigger the animation (?).
New to react native, not even sure if below is a good start to my animationgoal.
import React, { Component } from "react";
import { Animated } from "react-native";
import Images from "./assets/Images";
export default class Round extends Component {
constructor(props) {
super(props);
this.animatedValue = new Animated.Value(0);
}
render() {
let float = this.animatedValue.interpolate({
inputRange: [],
outputRange: [],
});
return (
<Animated.Image
style={{
position: "absolute",
left: x,
top: y,
width: width,
height: height,
transform: { translate: float },
}}
resizeMode="stretch"
source={Images.round}
/>
);
}
}
I used the react-native-animatable library to solve my problem
I have created something similar to iPhone delete app screen, where all icons wiggle.
You can decrease duration field to increase the speed of wiggle.
const spinValue = new Animated.Value(0);
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(spinValue, {
toValue: 1,
duration: 30,
useNativeDriver: true,
}),
Animated.timing(spinValue, {
toValue: 2,
duration: 40,
useNativeDriver: true,
}),
]),
).start();
}, [spinValue]);
const spin = spinValue.interpolate({
inputRange: [0, 1, 2],
outputRange: ['0deg', '2deg', '-2deg'],
});
**
JSX **
<
Animated.View
style = {
[
styles.container,
styles.deleteContainer,
{
transform: [{
rotate: spin
}],
},
]
} >
<
Image
style = {
[styles.thumb, styles.deleteContainer]
}
source = {
{
uri: item.thumbnailUrl
}
}
/> <
/Animated.View>
deleteContainer: {
opacity: 0.85,
borderRadius: 5,
borderColor: 'red',
borderWidth: 1.4,
},
I am creating pagination dots to show the flatlist content count but the issue is that translation is not smooth here is my code for the dots.
Dots.js
import React from 'react';
import {
Animated,
StyleSheet,
useWindowDimensions,
View,
ViewStyle,
} from 'react-native';
import { colors } from '../../global_constants/colors';
export interface ScalingDotProps {
data: Array<Object>;
scrollX: Animated.Value;
containerStyle?: ViewStyle;
dotStyle?: ViewStyle;
inActiveDotOpacity?: number;
inActiveDotColor?: string;
activeDotScale?: number;
activeDotColor?: string;
}
export const ScalingDot = ({
scrollX,
data,
dotStyle,
containerStyle,
inActiveDotOpacity,
inActiveDotColor,
activeDotScale,
activeDotColor,
}: ScalingDotProps) => {
const { width } = useWindowDimensions();
const defaultProps = {
inActiveDotColor: inActiveDotColor || '#347af0',
activeDotColor: activeDotColor || '#347af0',
animationType: 'scale',
inActiveDotOpacity: inActiveDotOpacity || 0.5,
activeDotScale: activeDotScale || 1.4,
};
return (
<View style={[styles.containerStyle, containerStyle]}>
{data.map((_, index) => {
const inputRange = [
(index - 1) * width,
index * width,
(index + 1) * width,
];
const colour = scrollX.interpolate({
inputRange,
outputRange: [
defaultProps.inActiveDotColor,
defaultProps.activeDotColor,
defaultProps.inActiveDotColor,
],
extrapolate: 'clamp',
});
const opacity = scrollX.interpolate({
inputRange,
outputRange: [
defaultProps.inActiveDotOpacity,
1,
defaultProps.inActiveDotOpacity,
],
extrapolate: 'clamp',
});
const scale = scrollX.interpolate({
inputRange: inputRange,
outputRange: [1, defaultProps.activeDotScale, 1],
extrapolate: 'clamp',
});
return (
<Animated.View
key={`dot-${index}`}
style={[
styles.dotStyle,
{ opacity },
{ transform: [{ scale }] },
dotStyle,
{backgroundColor: colors.primary}
// { backgroundColor: colour },
]}
/>
);
})}
</View>
);
};
const styles = StyleSheet.create({
containerStyle: {
position: 'absolute',
bottom: 20,
paddingBottom: 2,
flexDirection: 'row',
alignSelf: 'center',
},
dotStyle: {
width: 5,
height: 5,
borderRadius: 5/2,
marginHorizontal: 5,
},
});
I want animation to work on 60FPS, I used flatlist to render the data and enabled pagination.
using the scrollX value of flatlist to change the dots active status.
Solution is:
useNativeDriver: true
Animation in off the screen when it receives a positive value
Why it goes off-screen
For it to stay on screen I have to give it a negative value (e.g. -30)
const animation = useRef(new Animated.Value(0)).current;
const left = animation.interpolate({
inputRange: [0, 1],
outputRange: [0, 30],
});
const onClick = () => {
Animated.timing(animation, {
toValue: 30,
duration: 1000,
useNativeDriver: true,
}).start();
};
return (
<>
<View>
<Animated.View
style={{
backgroundColor: "red",
width: 100,
height: 100,
transform: [{ translateX: left }],
margin: 20,
}}
/>
</View>
</>
);
export default App;
const onClick = () => {
Animated.timing(animation, {
toValue: 1, //CHANGE THIS TO ONE IN YOUR CODE
duration: 1000,
useNativeDriver: true,
}).start();
};
you are interpolating on a value which goes to 30! but your input is 0,to 1!
i hope it helps
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,
......