I'm working with the animation of react-native lately and I'm trying to make a View component moving from side to side by click.
This is the code I have so far that works :
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View,
Animated,
Easing,
TouchableOpacity,
Dimensions
} from 'react-native';
export default class App extends Component {
constructor () {
super()
this.state = {
isLeftSide: true,
}
this.animatedValue = new Animated.Value(0)
}
animate () {
this.animatedValue.setValue(0);
Animated.timing(
this.animatedValue,
{
toValue: 1,
duration: 300,
easing: Easing.linear
}
).start()
}
fire = () => {
this.animate();
this.setState({isLeftSide: (!this.state.isLeftSide)});
}
direction = () => this.state.isLeftSide ? 'rtl' : 'ltr';
render() {
const screenWidth = Dimensions.get('screen').width;
const objectMaxCoord = screenWidth - 40;
const outputRange = {
rtl: [0, objectMaxCoord],
ltr: [objectMaxCoord, 0]
}
const marginLeft = this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: outputRange[this.direction()]
})
return (
<View style={styles.container}>
<TouchableOpacity onPress={() => this.fire()}><Text>Run</Text></TouchableOpacity>
<Animated.View
style={{
marginLeft,
height: 30,
width: 40,
backgroundColor: 'red'}} />
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
marginTop: 30
}
});
Now, the problem is that the red cube starts on the left side, but as I click run, it jumps (without animation) to the right corner and then move smoothly(in animation) to the left side. what comes after that works just perfect. Why is this first jump happens ?
And is there any easier way to make this animation done ?
( P.S I'm working on android )
Here is a link to an expo
https://snack.expo.io/S1aAgNq6Z
Probably because the state might not update right away. Try to change your fire function like this:
this.setState({isLeftSide: (!this.state.isLeftSide)}, () => {
this.animate();
});
setState accepts a callback when done.
I was playing with a Snack for a while and the issue is clearly that the state gets unsync because it says is on the left side when actually is on the right. I don't see anything else messing with the state.
You can see it here: https://snack.expo.io/BJhb4-pAW
The snack is somehow limited so can you try this on your code?
finishedAnimation = (finished) => {
if (finished)
this.setState({isLeftSide: !(this.state.isLeftSide)});
}
fire = () => {
this.animatedValue.setValue(0);
Animated.timing(
this.animatedValue,
{
toValue: 1,
duration: 300,
easing: Easing.linear
}
).start(this.finishedAnimation);
}
Related
I have a React Native component rendering a notification text. I want the text to fade in and fade out after a small delay. When a new notification text is set, I want to restart the same animation.
I have the following code:
export default function Notification({ notification }) {
if (!notification) {
return null;
}
const [fadeAnimation] = useState(new Animated.Value(0));
useEffect(() => {
Animated.sequence([
Animated.timing(fadeAnimation, {
toValue: 1,
duration: 500,
useNativeDriver: true,
}),
Animated.delay(3000),
Animated.timing(fadeAnimation, {
toValue: 0,
duration: 500,
useNativeDriver: true,
})]).start()
}, []);
return (
<Animated.View style={{ opacity: fadeAnimation, }}>
<View style={styles.notificaton}>
<Text style={styles.text}>{notification}</Text>
</View>
</Animated.View >
)
}
I read that I should be able to reset the animation with setValue(0) again, however I do not know where and when to call it.
work for me
import React, { Component } from 'react';
import { Animated, View } from 'react-native';
class Testing extends Component {
constructor(props) {
super(props);
this.animatedValue = new Animated.Value(0);
}
componentDidUpdate(prevProps) {
if (prevProps.myProp !== this.props.myProp) {
this.startAnimation();
}
}
startAnimation() {
this.animatedValue.setValue(0);
Animated.timing(this.animatedValue, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}).start();
}
render() {
const { myProp } = this.props;
const opacity = this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 1],
});
return (
<View>
<Animated.Text style={{ opacity }}>
My prop val : {myProp}
</Animated.Text>
</View>
);
}
}
export default Testing;
I am new to programming and my task is to animate the height of my image on scroll. i.e. decrease height on scroll down and increase height to original when scrolling up. However, following the React Native documentation on Animated, I replaced Text component with my Image and I'm unable to get the image showing. It shows when I don't wrap it in <Animated.View>, can anyone explain why and tell me how to fix? Thank you.
This is my current code just trying to do the sample fade animation before I try to animate height and running into issues getting the TopImage component showing:
import React, { useRef } from 'react';
import {
Animated,
View,
ScrollView,
StyleSheet,
} from 'react-native';
import { useHeaderHeight } from '#react-navigation/stack';
import TopImage from '../components/TopImage';
import GroceryList from '../components/GroceryList';
const App = () => {
const { theme } = useContext(ThemeContext);
const styles = createStyleSheet();
const fadeAnim = useRef(new Animated.Value(0)).current;
const fadeIn = () => {
Animated.timing(fadeAnim, {
toValue: 1,
duration: 5000,
useNativeDriver: true,
}).start();
};
const fadeOut = () => {
Animated.timing(fadeAnim, {
toValue: 0,
duration: 3000,
useNativeDriver: true,
}).start();
};
return (
<View style={styles.container}>
<Animated.View
style={
{
// Bind opacity to animated value
opacity: fadeAnim,
},
}>
<TopImage />
</Animated.View>
<ScrollView
style={styles.scrollContainer}
onScrollBeginDrag={() => fadeIn()}>
<GroceryList />
</ScrollView>
</View>
);
};
const createStyleSheet = () => {
const headerHeight = useHeaderHeight();
return StyleSheet.create({
container: { flex: 1 },
scrollContainer: { paddingTop: headerHeight + 100 },
});
};
export default App;
and this is the code for my TopImage component:
import React from 'react';
import { Image, StyleSheet } from 'react-native';
const topImage = require('../images/top.png');
const TopImage = () => {
const styles = createStyleSheet();
return <Image source={topImage} style={styles.image} />;
};
const createStyleSheet = () => {
return StyleSheet.create({
image: {
position: 'absolute',
width: '100%',
height: '50%',
},
});
};
export default TopImage;
The reason it's not showing is because your image is 'absolute' positioned and as such, the outer container (Animated.View) has no height.
If you apply a height to the Animated.View like below. You'll see that the opacity is actually working, you just couldn't see it before because the Animated.View was 0 pixels tall.
<Animated.View
style={
{
// Bind opacity to animated value
opacity: fadeAnim,
height: 300,
},
}>
<TopImage />
</Animated.View>
Alternatively you could make the image position relative, height 0 and animate it to the correct size.
I am using animation for up and down in the react native, But the animation just slide from up to down and then stop at the bottom i want to move it up and down continuously. I have also used animation loop so please check and provide me solution for this
import React, { useEffect, useState } from 'react'
import { Text, View, Animated, Easing, StyleSheet } from 'react-native'
import LoaderLogo from '../../icons/commonicons/LoaderLogo'
import { Loadericon } from '../../constants/Image';
import LinearGradient from 'react-native-linear-gradient';
import { dynamicSize } from '../sizechoose';
const amimationScreen = () => {
const startValue = new Animated.Value(0);
const endValue = dynamicSize(225);
const startValue2 = new Animated.Value(225);
const endValue2 = dynamicSize(0);
const duration = 5000;
useEffect(() => {
Animated.sequence([
Animated.timing(startValue, {
toValue: endValue,
duration: duration,
useNativeDriver: true,
}),
Animated.timing(startValue2, {
toValue: endValue2,
duration: duration,
useNativeDriver: true,
})
]).start()
}, [startValue, endValue, duration]);
return (
<Animated.View style={[{ transform: [{ translateY: startValue }] }]}>
<View style={{backgroundColor:'red',height:10,width:100}}>
</View>
</Animated.View>
)
}
export default amimationScreen
I also tried with react-native-animatable package but it is not good to use for me as it starts animation from the top of the screen.
This worked for me:
const App = () => {
const animated = new Animated.Value(0);
const duration = 5000;
useEffect(() => {
Animated.loop(
Animated.sequence([
Animated.timing(animated, {
toValue: 255,
duration: duration,
useNativeDriver: true,
}),
Animated.timing(animated, {
toValue: 0,
duration: duration,
useNativeDriver: true,
}),
]),
).start();
}, []);
return (
<Animated.View style={[{transform: [{translateY: animated}]}]}>
<View style={{backgroundColor: 'red', height: 10, width: 100}}></View>
</Animated.View>
);
};
So instead of having two instances of Animated.Value for translation, create one and let it transition from 0 to 255 and from 255 back to 0 in sequence. And make it loop once the sequence has finished.
I think the main problem in your original approach is that startValue decides how the view translates since this is what you pass as the value of translateY. The downward animation therefore happens correctly in your example. The upward animation however does not happen, because startValue2 is passed to Animated.timing and startValue is not used in the translation of any views in your example.
import React, { useEffect, useRef, useState } from 'react';
import { Animated, Dimensions, Easing, StyleSheet, View } from 'react-native';
export const App = () => {
const animatedValue = useRef(new Animated.Value(0)).current;
const [isTop, setIsTop] = useState(true);
const startAnimation = toValue => {
Animated.timing(animatedValue, {
toValue,
duration: 1000,
easing: Easing.linear,
useNativeDriver: true
}).start(() => {
setIsTop(!isTop);
})
}
useEffect(() => {
startAnimation(isTop ? 1 : 0);
}, [isTop]);
const translateY = animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, Dimensions.get('window').height - 70],
extrapolate: 'clamp'
})
return (
<View style={styles.container}>
<Animated.View style={[styles.square, { transform: [{ translateY }] }]}>
</Animated.View>
</View>
)
}
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center'
},
square: {
width: 70,
height: 70,
backgroundColor: 'red'
}
});
Can anyone share a code example for a react-native animation that moves an image from left to right, then move back to the starting point and repeat the motion?
Update:
The answer below helped a lot, but it didn't work. I used it to create the following animation that moves an image from left to right (I am using RN 0.62.2).
import React from 'react'
import { StyleSheet, View, Animated, Easing } from 'react-native';
const test = require('../images/test.png');
export class Splash extends React.Component {
constructor(props) {
super(props);
this.state = { xValue: new Animated.Value(-100) }
}
moveLR = () => {
Animated.timing(
this.state.xValue,
{
toValue: 100,
duration: 1000, // the duration of the animation
easing: Easing.linear, // the style of animation
useNativeDriver: true
}
).start();
}
moveRL = () => {
Animated.timing(
this.state.xValue,
{
toValue: -100,
duration: 3000, // the duration of the animation
easing: Easing.linear, // the style of animation
useNativeDriver: true
}
).start();
}
componentDidMount = () => {
this.moveLR();
}
render = () => {
return (
<View style={styles.mainContainer}>
<Animated.Image
style={{ width: 170, height: 146 }}
source={test}
style={{ width: 170, height: 146,
transform: [{ translateX: this.state.xValue }] }}>
</Animated.Image>
</View>
)
}
}
const styles = StyleSheet.create({
mainContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
animationView: {
backgroundColor: 'red',
width: 100,
height: 100
}
})
Update by Yossi:
The code below didn't work for me in RN 0.62.2. I am accepting the answer, but I modified it and the code that is working is included now in the question.
Original answer:
Before getting started, I need to introduce you to the two types of values for Animated animations:
Animated.Value () where we define a value, useful when we want to move an element on a single axis (X or Y), change the size of an element, etc. This is what we will use here, in this chapter, and this is what is used the most.
Animated.ValueXY () where we define a vector, useful for moving an element on two axes.
With these values, we can define several types of Animated animations. We will discover them one by one, testing them each time. in this example, I will only talk about Animated.timing ()
Here you can see an example of code which is gonna moove a red box from left to right and stop when the user decides, you can try it and tell if it worked for you :
// Test.js
import React from 'react'
import { StyleSheet, View, Animated, TouchableOpacity, Text, Easing } from 'react-native'
class Test extends React.Component {
constructor(props) {
super(props);
this.state = {
leftPosition : new Animated.Value (0)
}
this.mooveLR = this.mooveLR.bind(this);
this.mooveRL = this.mooveRL.bind(this);
this.stopAnimation = this.stopAnimation.bind(this);
}
stopAnimation () {
this.setState({
leftPosition : this.state.leftPosition // this forces the left position to remain the same considering the `componentDidMount` method already happened
})
}
mooveLR (){
Animated.timing(
this.state.leftPosition,
{
toValue: 100,
duration: 3000, // the duration of the animation
easing: Easing.linear, // the style of animation
}
).start() // starts this annimation once this method is called
}
mooveRL (){
Animated.timing(
this.state.leftPosition,
{
toValue: 0,
duration: 3000, // the duration of the animation
easing: Easing.linear, // the style of animation
}
).start() // starts this annimation once this method is called
}
componentDidMount(){
this.state.leftPosition === 0 ? this.mooveLR () : this.mooveRL () // repeats always when the red box return to its initial position : leftPosition === 0
}
render() {
return (
<View style={styles.main_container}>
<Animated.View style={[styles.animation_view, {left : this.state.leftPosition}]}>
</Animated.View>
<TouchableOpacity onPress = { () => this.stopAnimation ()}>
<Text>Stop animation</Text>
</TouchableOpacity>
</View>
)
}
}
const styles = StyleSheet.create({
main_container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center'
},
animation_view: {
backgroundColor: 'red',
width: 100,
height: 100
}
})
export default Test;
Hope it's gonna help
Regards
I have resolved this issue by doing:
import {
Easing
} from 'react-native';
Since I prefer using react hooks, I converted the solution Provided by #pacy.eth, and here it is for anyone who prefers to use react hooks.
import { View, Text, Animated, Easing } from 'react-native';
import React, { useEffect, useState } from 'react';
const Animate= ({ children, left, leftP, duration }) => {
const [leftPosition, setLeftPosition] = useState(new Animated.Value (leftP));
useEffect(() => {
left ? mooveLR() : mooveRL();
}, []);
const mooveLR = () => {
Animated.timing(leftPosition, {
toValue: 100,
duration, // the duration of the animation
easing: Easing.linear, // the style of animation
}).start(); // starts this annimation once this method is called
};
const mooveRL = () => {
Animated.timing(leftPosition, {
toValue: 0,
duration, // the duration of the animation
easing: Easing.linear, // the style of animation
}).start(); // starts this annimation once this method is called
};
return (
<Animated.View style={[{ left: leftPosition }]}>{children}</Animated.View>
);
};
export default Animate;
And with a little modifications, I made it reusable in severals ways:
one of my favorite ways, is by wrapping the component that I want to animate and I pass the the direction "left: true or false" I set the "leftP" which is the leftPosition (in my case I am hiding the view and with a click of a button I slide it in with the Animate component created) and the "duration" of the animation.
for ex:
<Animate left={false} duration={1000} leftP={-Dimensions.get('window').width}>
...
</Animate>
I use Animated.Text for change Animation Text but it's not working properly
I also require in animation fade out old text & fade in the new text.
import React, { Component, PropTypes } from 'react';
import {
StyleSheet,
View,
Text,
Image,
Dimensions,
Animated
} from 'react-native';
import styles from './styles';
const win = Dimensions.get('window');
export default class Logo extends Component {
constructor(props) {
super(props);
this.tempText = new Animated.Value("Hello");
}
componentWillMount () {
Animated.timing(this.tempText, {
duration: 5000,
toValue: "New Text",
}).start();
};
render() {
return (
<View style={{flex:1}}>
<Animated.Text style={{color: "#9b9b9b"}}>
{this.tempText}
</Animated.Text>
</View>
);
}
}
Actual output Get - Change text after 5 Second but it's not working.please help me.
What you're trying to achieve can be done without using Animated at all, and actually, Animated isn't intended for this particular use.
Replacing the text can be done with a simple variable, and the text change can be triggered by a setTimeout.
Animated is intended for changing a numeric value, not a text value. Think of it this way - if the change is supposed to happen over a 5 second interval, what would the mid-value be?
Do this instead:
export default class Logo extends Component {
constructor(props) {
super(props);
this.state = {text: "Hello"};
}
componentDidMount () {
setTimeout(() => this.setState({text: "New Text"}), 5000);
};
render() {
return (
<View style={{flex:1}}>
<Animated.Text style={{color: "#9b9b9b"}}>
{this.state.text}
</Animated.Text>
</View>
);
}
}
My example with smoothly opacity animation.
Sorry, without fadeIn, fadeOut.
const inputRange = [0, 1, 2, 3];
const AnimatedText = Animated.createAnimatedComponent(Text);
const animationProps = {
duration: 500,
easing: Easing.out(Easing.linear),
isInteraction: false,
useNativeDriver: true,
};
class Onboarding extends PureComponent {
activeDot = 0;
animationDot = new Animated.Value(0);
toggleOnButton = () => {
Animated.timing(this.animationDot, {
toValue: this.activeDot + 1,
...animationProps,
}).start((endState) => {
if (endState.finished) {
this.activeDot = this.activeDot + 1;
}
});
}
renderButton = () => {
const opacityNext = this.animationDot.interpolate({
inputRange,
outputRange: [1, 1, 1, 0]
});
const opacityGetStarted = this.animationDot.interpolate({
inputRange,
outputRange: [0, 0, 0, 1]
});
return (
<TouchableOpacity style={styles.button} onPress={this.toggleOnButton}>
<AnimatedText style={[styles.buttonText, { opacity: opacityNext }]}>
Next
</AnimatedText>
<AnimatedText style={[styles.buttonText, {
top: normalize(isIOS ? 12 : 8), position: 'absolute', opacity: opacityGetStarted
}]}
>
Get started
</AnimatedText>
</TouchableOpacity>
);
}
}