react native animated repeat infinity - react-native

I have the following animation.
componentWillMount(){
this.animatedValue = new Animated.Value(300);
}
componentDidMount(){
Animated.timing( this.animatedValue , {
toValue: -100,
duration: 3000,
} ).start();
}
render() {
const animatedStyle = { marginLeft: this.animatedValue, marginRight: - this.animatedValue };
return (
<View style={{flexDirection: 'row', height: 100,}}>
<Animated.View style={[ animatedStyle,{backgroundColor: 'blue', width:100}]} />
</View>
);
}
I would like to repeat endless times. Anyone have any suggestions?

2019 solution:
Animated.loop(Animated.timing(this.animatedValue , {
toValue: -100,
duration: 3000,
})).start();

Pass a callback to Animated.start() that resets the Animated value and starts the animation again. For example:
componentDidMount(){
this.runAnimation();
}
runAnimation() {
this.animatedValue.setValue(300);
Animated.timing(this.animatedValue, {
toValue: -100,
duration: 3000,
}).start(() => this.runAnimation());
}
If you need to stop the animation at any point, take a look at this question/answer.

Related

Smooth rotation of React Native SVG Component [duplicate]

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 šŸ‘

Component Re-renders onChangeText

I made a custom TextInput that I'm animating into view on my Registration screen component. The issue is that for every keystroke onChangeText, the animation plays again and this is not the expected behavior. If I take out the animation, the TextInput works fine.
I've tried changing my dependency on my useState, also tried to wrap the component with a useCallback and useMemo and none has gotten it to work.
Also worthy of note is that I'm handling my state management with useReducer. Code below
const RegistrationScreen = ({ navigation }: any) => {
const textPosition = new Animated.Value(width);
const inputPosition = new Animated.Value(width);
const inputPosition2 = new Animated.Value(width);
const inputPosition3 = new Animated.Value(width);
const inputPosition4 = new Animated.Value(width);
const [state, dispatch] = useReducer(reducer, initialState);
const { name, email_address, phone_number, password } = state;
const animate = Animated.sequence([
Animated.parallel([
Animated.timing(textPosition, {
toValue: 0,
delay: 50,
duration: 1000,
useNativeDriver: true,
easing: Easing.elastic(3),
}),
Animated.timing(inputPosition, {
toValue: 0,
delay: 100,
duration: 1000,
useNativeDriver: true,
easing: Easing.elastic(3),
}),
Animated.timing(inputPosition2, {
toValue: 0,
delay: 200,
duration: 1000,
useNativeDriver: true,
easing: Easing.elastic(3),
}),
Animated.timing(inputPosition3, {
toValue: 0,
delay: 300,
duration: 1000,
useNativeDriver: true,
easing: Easing.elastic(3),
}),
Animated.timing(inputPosition4, {
toValue: 0,
delay: 400,
duration: 1000,
useNativeDriver: true,
easing: Easing.elastic(3),
}),
]),
]);
const _onCreateAccountHandler = () => dispatch(getPhoneVerificationCode(phone_number));
const _onChangeHandler = (field: any, value: any) => dispatch({ type: 'FIELD', field, value });
useEffect(() => {
animate.start();
}, [animate]);
return (
<SafeAreaView style={{ flex: 1 }}>
<KeyboardAvoidingView
style={{ flex: 1 }}
keyboardVerticalOffset={100}
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}>
<View style={styles.container}>
<View style={{ flex: 1 }}>
<Header
backButtonEnabled
backButtonColor={colors.darkGray}
onBackButtonPress={() => navigation.goBack(null)}
/>
</View>
<View style={styles.innerContainer}>
<Animated.View
style={[
styles.createAccount,
{
transform: [
{
translateX: textPosition,
},
],
},
]}>
<Text style={styles.creaetAccountText}>{strings.create_an_account}</Text>
</Animated.View>
<View style={styles.textAreaContainer}>
<Animated.View
style={[
styles.textInputContainer,
{
transform: [
{
translateX: inputPosition,
},
],
},
]}>
<TextInput
placeHolder={strings.name}
value={name}
onChangeText={(text: any) => _onChangeHandler('name', text)}
onCancelPressed={() => {}}
placeHolderStyle={{
backgroundColor: colors.lightWhite,
}}
autoCorrect={false}
/>
</Animated.View>
You could try wrapping animate with React.useCallback()
So From further research, it turns out that since I'm animating the component with a value that is always constant (width), onTextChange will always rerender the screen and any value that is different will be accounted for, hence, the animation plays again since the current value is different from the initial value.
My Solution:
I had to use useState to change the initial value as soon as the first animation completes. I also had to call that in a setTimeout that runs after the animation that way - the initial width will update to become the current width and when the component re-renders, it won't render again. (or in this case, there's no difference in the translate X value, so animation won't play again)

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

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?
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>
);
}
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>
);

React Native, Multiple Animations OnPress

Thanks in advance for the help.
Attempting to have an image asset (in this case "yes.png") trigger two animations on touch ("onPress).
The image itself should spring (animated.spring) and then previously hidden text should appear.
Whatever I try I can only get it to do one or the other but not both.
Tried wrap both animations in a parallel aka
_springAnimation = () =>
Animated.parallel([
Animated.spring(this.state.springValue, {
toValue: 1.33,
friction: 0.47,
useNativeDriver: true,
}),
Animated.timing(this.state.fadeValue, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}).start(),
]);
and add <TouchableOpacity onPress={this._start}> after the first.
Here is the current code I have:
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
springValue: new Animated.Value(0.47),
useNativeDriver: true,
fadeValue: new Animated.Value(0),
};
}
_springAnimation = () => {
Animated.spring(this.state.springValue, {
toValue: 1.33,
friction: 0.47,
useNativeDriver: true,
}).start();
};
_start = () => {
Animated.timing(this.state.fadeValue, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
}).start();
};
render() {
return (
<View style={styles.container}>
<TouchableOpacity onPress={this._springAnimation}>
<Animated.Image
source={require('animation/assets/yes.png')}
style={[
styles.imageView,
{
transform: [{scale: this.state.springValue}],
},
]}
/>
<Animated.View
style={{
opacity: this.state.fadeValue,
height: 60,
width: 200,
backgroundColor: 'black',
justifyContent: 'center',
}}>
<Text style={styles.buttonText}>Win</Text>
</Animated.View>
</TouchableOpacity>
</View>
);
}
}
If you want to run animation one after another. You can do this by Animated.sequence.
Animated.sequence([
Animated.spring(this.state.springValue, {
toValue: 1.33,
friction: 0.47,
useNativeDriver: true,
}),
Animated.timing(this.state.fadeValue, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
})
]).start(); // start the sequence group
They will start stop accordingly.
And If you want to run it parallel then try below code.
Animated.parallel([
Animated.spring(this.state.springValue, {
toValue: 1.33,
friction: 0.47,
useNativeDriver: true,
}),
Animated.timing(this.state.fadeValue, {
toValue: 1,
duration: 1000,
useNativeDriver: true,
})
]).start(); // start the sequence group

React Native Animations simply not running

My issue with React Native seems to be project wide. Anything using the Animated API simply does not run. I am running React Native 0.49.2,
Nothing seems to be working, I have tried out several peoples code, with nothing ever happening. The issue to me seems to be whenever I call "Animated.Timing().start();" it never actually starts. Heres some short example code:
class Splash extends React.Component {
constructor(props){
super(props);
this.state = {
ShowSetup: true,
fadeAnim: new Animated.Value(0), // Initial value for opacity: 0
};
}
componentDidMount(){
this.Animator()
}
Animator(){
console.log("ANIMATOR RUNNING!")
Animated.timing( // Animate over time
this.state.fadeAnim, // The animated value to drive
{
toValue: 1, // Animate to opacity: 1 (opaque)
duration: 10000, // Make it take a while
}
).start(); // Starts the animation
}
render() {
let { fadeAnim } = this.state;
return (
<View style={{flex: 1, backgroundColor: 'blue'}}>
<Animated.View // Special animatable View
style={{
opacity: fadeAnim, // Bind opacity to animated value
}}
>
<Text>Fade In</Text>
</Animated.View>
</View>
);
}
}
No matter how long you wait, the value will not show up. Has become a big head scratcher for me as I am not sure what else to do
try this:
<View style={{flex: 1, backgroundColor: 'blue'}}>
<Animated.View // Special animatable View
style={{
opacity: this.state.fadeAnim, // Bind opacity to animated value
}}
>
<Text>Fade In</Text>
</Animated.View>
</View>
I think the fadeAnim variable you declare at the top of your render isn't being reassigned
-- EDIT --
Here is some animation code I've used, the render is implemented the same way you have it.
constructor() {
super();
this.state = {
topBoxOpacity: new Animated.Value(0),
leftBoxOpacity: new Animated.Value(0),
mainImageOpacity: new Animated.Value(0),
}
}
componentDidMount() {
const timing = Animated.timing;
Animated.parallel([
timing(this.state.leftBoxOpacity, {
toValue: 1,
duration: 700,
delay: 700,
}),
timing(this.state.topBoxOpacity, {
toValue: 1,
duration: 700,
delay: 1400,
}),
timing(this.state.mainImageOpacity, {
toValue: 1,
duration: 700,
delay: 2100,
}),
]).start();
}