Using React Native Animated to fade in and out elements - javascript

I am new to react native animated feature. I am hoping to use it to fade in and out elements on button press. The below code works to fade in the element when the page loads, but the button to fade it out does not fade it out as I would expect. Can someone explain what I am doing wrong. Thank you.
class FadeComponent extends Component {
constructor(props) {
super(props)
this.state = {
fadeAnim: new Animated.Value(0), // Initial value for opacity: 0
}
this.fadeOut = this.fadeOut.bind(this);
}
componentDidMount() {
Animated.timing( // Animate over time
this.state.fadeAnim, // The animated value to drive
{
toValue: 1, // Animate to opacity: 1 (opaque)
duration: 2000, // 2000ms
}
).start(); // Starts the animation
}
fadeOut(){
this.setState({fadeAnim: new Animated.Value(1)})
Animated.timing( // Animate over time
this.state.fadeAnim, // The animated value to drive
{
toValue: 0, // Animate to opacity: 1 (opaque)
duration: 2000, // 2000ms
}
).start(); // Starts the animation
}
render() {
let { fadeAnim } = this.state;
return (
<View style = {{ backgroundColor: '#1a8cff', marginTop: 100 }}>
<Animated.View style={{ ...this.props.style, opacity: fadeAnim }} >
{this.props.children}
<View style = {{ backgroundColor: '#000000', height: 50, width: 50 }}>
</View>
</Animated.View>
<TouchableOpacity onPress={this.fadeOut} >
<Text style={{ color: 'white', textDecorationLine: 'underline', marginTop: 10 }}>
fade out
</Text>
</TouchableOpacity>
</View>
);
}
}

Because function setState is asynchronous. This should fix your issue:
this.setState({ fadeAnim: new Animated.Value(1) },
() => {
Animated.timing( // Animate over time
this.state.fadeAnim, // The animated value to drive
{
toValue: 0, // Animate to opacity: 1 (opaque)
duration: 2000, // 2000ms
}
).start();
})

Related

How do i loop animated-circular-progress component

I'm using the react-native-circular-progress component and I'm trying to loop an animation every 30 seconds.
i.e the animation is 30 seconds long and i want it to restart as soon as its done
The component exposes a function named reAnimate which I have put in a setInterval function as soon as the component mounts.
import React from 'react';
import { StyleSheet, Text, View,Dimensions, Easing } from 'react-native';
import { AnimatedCircularProgress } from 'react-native-circular-progress';
const MAX_POINTS = 30;
export default class App extends React.Component {
state = {
isMoving: false,
pointsDelta: 0,
points: 1,
};
componentDidMount(){
setInterval(
() => this.circularProgress.reAnimate(0,100, 30000,Easing.linear),
30000
);
}
render() {
const { width } = Dimensions.get("window");
const window = width - 120;
const fill = (this.state.points / MAX_POINTS) * 100;
return (
<View style={styles.container} >
<AnimatedCircularProgress
size={window}
width={7}
backgroundWidth={5}
fill={0}
tintColor="#ef9837"
backgroundColor="#3d5875"
ref={(ref) => this.circularProgress = ref}
arcSweepAngle={240}
rotation={240}
lineCap="round"
>
{fill => <Text style={styles.points}>{Math.round((MAX_POINTS * fill) / 100)}</Text>}
</AnimatedCircularProgress>
</View>
);
}
}
const styles = StyleSheet.create({
points: {
textAlign: 'center',
color: '#ef9837',
fontSize: 50,
fontWeight: '100',
},
container: {
flex: 1,
justifyContent: 'space-between',
alignItems: 'center',
backgroundColor: '#0d131d',
padding: 50,
},
pointsDelta: {
color: '#4c6479',
fontSize: 50,
fontWeight: '100',
},
pointsDeltaActive: {
color: '#fff',
},
});
This is working... BUT... the animation only starts 30s after the component mounts. How do i get it to loop immediately?
Any help will be greatly appreciated.
Thank you.
The reason is setInterval will not fire immediately it will start after the duration you passed i.e 30 mins, So all you have to do is make a call initially before setting the interval, also don't forget to clear the interval.
Here's how I would it:
componentDidMount(){
this.circularProgress.animate(100, 30000,Easing.linear);
this.intervalId = setInterval(
() => this.circularProgress.reAnimate(0,100, 30000,Easing.linear),
30000
);
}
componentWillUnmount() {
clearInterval(this.intervalId);
}

Apply different animations on different items in a screen of React native app

I've tried to apply animations on 2 items in a screen. There are two images and I want to apply animations on both of them simultaneously but different animations. One should slide from left and other from right together.
I've searched for different sources but found nothing. There are examples to apply multiple animations together but those will be applied to all the items in <Animated.View>
export default class App extends React.Component {
componentWillMount() {
this.animatedMargin = new Animated.Value(0);
// I want to fire both animations from here
setTimeout(() => {
// this._slideAnimation('left');
// this._slideAnimation('right');
// I actually want to know how to achieve this
}, 100);
}
_slideAnimation(direction) {
if (direction === 'left'){
Animated.timing(this.animatedMargin, {
toValue: width,
duration: 1000
}).start();
} else {
Animated.timing(this.animatedMargin, {
toValue: 0,
duration: 1000
}).start();
}
}
render() {
return (
<View style={{ styles.container }}>
{/* I want to slide this from left to right */}
<Animated.View style={[ styles.ainmated, { left: this.animatedMargin } ]}>
<Image source={ left_image } />
</Animated.View>
{/* and this one in reverse direction */}
<Animated.View style={[ styles.ainmated, { right: this.animatedMargin } ]}>
<Image source={ right_image } />
</Animated.View>
</View>
);
}
}
But in this way, only one animation can be applied at once. I've evern tried Animated.parallel so that multiple animations can be applied in parallel but Both <Animated.View> tag will be animated with same animation rather than separate ones
So, how can I achieve different animations on different objects/components in a single screen simultaneously in React native?
You don't need two animations to achieve this.
Here is a full working example:
import * as React from 'react';
import { Text,Animated,View, StyleSheet, Dimensions } from 'react-native';
import Constants from 'expo-constants';
const { width } = Dimensions.get('screen')
const ITEM_SIZE = 60
export default class App extends React.Component {
componentWillMount() {
this.animatedMargin = new Animated.Value(0);
setTimeout(() => {
this._slideAnimation();
}, 1000);
}
_slideAnimation() {
Animated.timing(this.animatedMargin, {
toValue: (width / 2) - ITEM_SIZE,
duration: 1000
}).start()
}
render() {
return (
<View style={ styles.container }>
<Animated.View style={[ styles.animated, { left: this.animatedMargin } ]}>
<View style={[styles.imagePlaceholder, { backgroundColor: '#0070ff'}]} />
</Animated.View>
<Animated.View style={[ styles.animated, { right: this.animatedMargin } ]}>
<View style={[ styles.imagePlaceholder, {backgroundColor: '#008080'} ]} />
</Animated.View>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
},
animated: {
position: 'absolute'
},
imagePlaceholder: {
width: ITEM_SIZE,
height: ITEM_SIZE
}
});

.setState is not a function in React Native

I've looked through many other threads with this same issue, but none seem to have the solution I'm looking for. I followed a tutorial off of YouTube (Fullstack Development is the channel, and it is how to make a game in 28 minutes). My main code looks like this:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Dimensions,
Animated,
Image,
} from 'react-native';
import Enemy from './app/components/Enemy';
export default class Blox extends Component {
constructor(props){
super(props);
this.state = {
movePlayerVal: new Animated.Value(40),
playerSide:'left',
points: 0,
moveEnemyVal: new Animated.Value(0),
enemyStartposX: 0,
enemySide: 'left',
enemySpeed: 4200,
gameOver: false,
};
}
render() {
return (
<Image source = {require('./app/img/bg.png')} style={styles.container}>
<View style= {{ flex:1, alignItems: 'center', marginTop: 80 }}>
<View style={styles.points}>
<Text style={{ fontWeight: 'bold', fontSize: 40 }}>{this.state.points}</Text>
</View>
</View>
<Animated.Image source={require('./app/img/car.png')}
style = {{
height:100,
width:100,
position:'absolute',
zIndex:1,
bottom: 50,
resizeMode: 'stretch',
transform: [
{ translateX: this.state.movePlayerVal }
]
}}></Animated.Image>
<Enemy enemyImg={require('./app/img/enemy.png')}
enemyStartposX={this.state.enemyStartposX}
moveEnemyVal={this.state.moveEnemyVal} />
<View style={styles.controls}>
<Text style = {styles.left} onPress={ () => this.movePlayer('left') }> {'<'} </Text>
<Text style={styles.right} onPress={ () => this.movePlayer('right') }> {'>'} </Text>
</View>
</Image>
);
}
movePlayer(direction) {
//move player right
if (direction == 'right') {
this.setState({ playerSide: 'right' }); //issue with setState being used as a function (from lines 78-124)
Animated.spring(
this.state.movePlayerVal,
{
toValue: Dimensions.get('window').width = 140,
tension: 120,
}
).start();
} else if (direction == 'left') {
this.setState({ playerSide: 'left' });
Animated.spring(
this.state.movePlayerVal,
{
toValue: 40,
tension: 120,
}
).start();
}
}
componentDidMount() {
this.animateEnemy();
}
animateEnemy() {
this.state.moveEnemyVal.setValue(-100);
var windowH = Dimensions.get('window').height;
//Generate left distance for enemy
var r = Math.floor(Math.random() * 2) * 1;
if (r == 2) {
r = 40;
this.setState({ enemySide: 'left' });
} else {
r = Dimensions.get('window').width = 140;
//Enemy is on the right
this.setState = ({ enemySide: 'right' });
}
this.setState({ enemyStartposX: r }); //issue with this
//Interval to check for collision each 50 ms
var refreshIntervalId;
refreshIntervalId = ( () => {
//Collision logic
//If enemy collides with player and they are on the same side
//and the enemy has not passed the player safely
if (this.state.moveEnemyVal._value > windowH - 280 && this.state.moveEnemyVal._value < windowH -180 && this.state.playerSide == this.state.enemySide) {
clearInterval(refreshIntervalId)
this.setState({ gameOver: true });
this.gameOver();
}
}, 50);
//Increase enemy speed each 20th second
setInterval ( () => {
this.setState({ enemySpeed: this.state.enemySpeed - 50 })
}, 20000);
//Animate the enemy
Animated.timing(
this.state.moveEnemyVal,
{
toValue: Dimensions.get('window').height,
duration: this.state.enemySpeed,
}
).start(event => {
//if no enemy collision is detected, restart enemy animation
if (event.finished && this.state.gameOver == false) {
clearInterval(refreshIntervalId);
this.setState({ points: ++this.state.points });
this.animateEnemy();
}
});
}
gameOver() {
alert('You lost big time!');
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
position: 'relative',
resizeMode: 'cover',
},
points: {
width: 80,
height: 80,
backgroundColor: '#fff',
borderRadius: 100,
alignItems: 'center',
justifyContent: 'center',
},
controls: {
alignItems: 'center',
flexDirection: 'row',
},
right: {
flex: 1,
color: '#fff',
margin: 0,
fontSize: 60,
fontWeight: 'bold',
textAlign: 'left'
},
left: {
flex: 1,
color: '#fff',
fontSize: 60,
fontWeight: 'bold',
textAlign: 'right'
},
});
I also have an enemy class that looks like this:
import React, { Component } from 'react';
import {
AppRegistry,
StyleSheet,
Text,
Animated,
Image,
} from 'react-native';
export default class Enemy extends Component {
render() {
return (
<Animated.Image source={this.props.enemyImg}
style = {{
height:100,
width:100,
position:'absolute',
resizeMode: 'stretch',
left: this.props.enemyStartposX,
transform: [
{ translateY: this.props.moveEnemyVal },
]
}}></Animated.Image>
);
}
}
I'm running this through Expo XDE, and as I was building it, it worked. However, once I started coding the animation, Expo turned red and said I had an error in line 124 (in App.js), that .setState is not a function, it is an object. I commented it out, and it told me I had the same error in line 78 of my App.js file. Can you tell me what .setState is, and how to potentially fix this error? Thanks.
You have a wrong setState call in your animateEnemy function. That's why you got that error message.
animateEnemy() {
...
if (r == 2) {
r = 40;
this.setState({ enemySide: 'left' });
} else {
r = Dimensions.get('window').width = 140;
// Enemy is on the right
// this.setState = ({ enemySide: 'right' }); // This is wrong
this.setState({ enemySide: 'right' });
}
...
}
And it would be much better if you change all of the function with arrow function animateEnemy = () => { ... }. With this way, you don't need to manually bind it in the constructor.
You should bind the movePlayer function to the proper scope in your constructor:
constructor() {
this.movePlayer = this.movePlayer.bind(this);
}
I think your problem is how you handle the method callback. You just bind method movePlayer() and animateEnemy() in constructor like :
constructor(props) {
super(props);
this.state = {
movePlayerVal: new Animated.Value(40),
playerSide:'left',
points: 0,
moveEnemyVal: new Animated.Value(0),
enemyStartposX: 0,
enemySide: 'left',
enemySpeed: 4200,
gameOver: false,
};
// This binding is necessary to make `this` work in the callback
this.movePlayer = this.movePlayer.bind(this);
this.animateEnemy = this.animateEnemy.bind(this);
}
maybe you can learn deeply about how your handle method in ReactNative in this.
i hope my answer can help you..

The spin animation set on the image is not working in react-native

I am trying to spin an Image it is basically to show that a coin is flipped ( coin Tossing animation ) I have applied this basic animation to the image but it is not getting animated,
The image is stationary while I tested it on emulator
this is my index.android.js file :
import React, { Component } from 'react';
import {
AppRegistry,
View,
Animated,
Easing
} from 'react-native';
export default class animateTest extends Component {
constructor(props) {
super(props);
this.spinValue = new Animated.Value(0);
}
spin() {
this.spinValue.setValue(0);
Animated.timing(
this.spinValue, {
toValue: 1,
duration: 1500,
useNativeDriver: true,
easing: Easing.linear
}
).start();
}
render() {
const spin = this.spinValue.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
});
return (
<View style={styles.ViewStyle}>
<Animated.Image
style={[
styles.coinStyle,
{
transform: [
{ rotate: spin }
]
}
]}
source={require('./Images/Coin_Tail.png')}
style={styles.coinStyle} />
</View>
);
}
}
const styles = {
coinStyle: {
width: 150,
height: 150,
},
ViewStyle: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'black'
}
};
AppRegistry.registerComponent('animateTest', () => animateTest);
You code have 2 issues:
1) In your render function, you have a duplicated style prop for your image that override the first style with transform styling. To fix it, remove the second style prop
2) Your code did not trigger the spin animation, you can add a touchable with on press event to call your spin method. For quick test, you can add
componentDidMount() {
this.spin();
}

React Native: How do you animate the rotation of an Image?

Rotation is a style transform and in RN, you can rotate things like this
render() {
return (
<View style={{transform:[{rotate: '10 deg'}]}}>
<Image source={require('./logo.png')} />
</View>
);
}
However, to animate things in RN, you have to use numbers, not strings. Can you still animate transforms in RN or do I have to come up with some kind of sprite sheet and change the Image src at some fps?
You can actually animate strings using the interpolate method. interpolate takes a range of values, typically 0 to 1 works well for most things, and interpolates them into a range of values (these could be strings, numbers, even functions that return a value).
What you would do is take an existing Animated value and pass it through the interpolate function like this:
spinValue = new Animated.Value(0);
// First set up animation
Animated.timing(
this.spinValue,
{
toValue: 1,
duration: 3000,
easing: Easing.linear, // Easing is an additional import from react-native
useNativeDriver: true // To make use of native driver for performance
}
).start()
// Next, interpolate beginning and end values (in this case 0 and 1)
const spin = this.spinValue.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg']
})
Then use it in your component like this:
<Animated.Image
style={{transform: [{rotate: spin}] }}
source={{uri: 'somesource.png'}} />
In case if you want to do the rotation in loop, then add the Animated.timing in the Animated.loop
Animated.loop(
Animated.timing(
this.spinValue,
{
toValue: 1,
duration: 3000,
easing: Easing.linear,
useNativeDriver: true
}
)
).start();
Don't forget to add property useNativeDriver to ensure that you get the best performance out of this animation:
// First set up animation
Animated.timing(
this.state.spinValue,
{
toValue: 1,
duration: 3000,
easing: Easing.linear,
useNativeDriver: true
}
).start();
A note for the newbies like me:
For animating something else you need to wrap it in <Animated.SOMETHING> for this to work. Or else the compiler will panic on that transform property:
import {Animated} from 'react-native';
...
//animation code above
...
<Animated.View style={{transform: [{rotate: spinValue}] }} >
<YourComponent />
</Animated.View>
BUT for an image (Animated.Image), the example above is 100% goodness and correct.
Since most of the answers are functions & hooks based, herewith a complete example of class based Animation of Image.
import React from 'react';
import {
SafeAreaView,
View,
Animated,
Easing,
TouchableHighlight,
Text,
} from 'react-native';
export default class App extends React.Component {
constructor(props) {
super(props);
this.state = {
rotateValueHolder: new Animated.Value(0)
};
}
componentDidMount = () => {
this.startImageRotateFunction();
}
startImageRotateFunction = () => {
Animated.loop(Animated.timing(this.state.rotateValueHolder, {
toValue: 1,
duration: 3000,
easing: Easing.linear,
useNativeDriver: false,
})).start();
};
render(){
return(
<SafeAreaView>
<View>
<Animated.Image
style={{
width: 200,
height: 200,
alignSelf:"center",
transform:
[
{
rotate: this.state.rotateValueHolder.interpolate(
{
inputRange: [0, 1],
outputRange: ['0deg', '360deg'],
}
)
}
],
}}
source={{uri:'https://raw.githubusercontent.com/AboutReact/sampleresource/master/old_logo.png',}}
/>
<TouchableHighlight
onPress={() => this.startImageRotateFunction()}>
<Text style={{textAlign:"center"}}>
CLICK HERE
</Text>
</TouchableHighlight>
</View>
</SafeAreaView>
);
}
}
Just gonna drop the solution I solved by stitching together parts from the answers here.
import { Feather } from '#expo/vector-icons'
import * as React from 'react'
import { TextStyle, Animated, Easing } from 'react-native'
import { Colors, FontSize } from '~/constants/Theme'
export const LoadingSpinner = React.memo(
({ color = Colors['sand'], size = FontSize['md'] - 1, fadeInDelay = 1000, ...props }: Props) => {
const fadeInValue = new Animated.Value(0)
const spinValue = new Animated.Value(0)
Animated.sequence([
Animated.delay(fadeInDelay),
Animated.timing(fadeInValue, {
toValue: 1,
duration: 1500,
easing: Easing.linear,
useNativeDriver: true,
}),
]).start()
Animated.loop(
Animated.timing(spinValue, {
toValue: 360,
duration: 300000,
easing: Easing.linear,
useNativeDriver: true,
})
).start()
return (
<Animated.View
style={{
opacity: fadeInValue,
transform: [{ rotate: spinValue }],
}}
>
<Feather
name="loader"
size={size}
style={{
color,
alignSelf: 'center',
}}
{...props.featherProps}
/>
</Animated.View>
)
}
)
type Props = {
color?: TextStyle['color']
size?: number
featherProps?: Partial<Omit<React.ComponentProps<typeof Feather>, 'style'>>
fadeInDelay?: number
}
Hope it helps 👍

Categories

Resources