How to speed up animation based on a count down timer? - react-native

I have a button in my screen that has a movement animation that keeps going right and left to encourage the user to touch that. Also, I want to have a count down timer, so when time decrease button animation speed up.
I did this :
constructor(props) {
super(props);
this.state = {
timer : 1000,
animateBtnOnTime : new Animated.Value(1),
};
}
const ButtonAnimation =
Animated.loop(
Animated.timing(
this.state.animateBtnOnTime,
{
toValue: 2,
friction: 1,
tension: 1,
duration: this.state.timer
}
)
);
componentDidMount(){
this._runAnimation();
this.interval = setInterval(
() => this.setState((prevState)=> ({ timer: prevState.timer - 100 })),
1000
);
}
componentDidUpdate(){
if(this.state.timer === 100){
clearInterval(this.interval);
}
}
componentWillUnmount(){
clearInterval(this.interval);
}
But it won't work, speed won't change and it keeps going in start speed.
could you please help me to achieve this?
thanks.

Related

React native - stopping an animation

I'm trying to stop the animation once the data has loaded. For the sake of testing, I'm using a timer to simulate state change (data has loaded) to interrupt animation. The problem is that the animation keeps running after the state has changed.
const [isLoading, setIsLoading] = useState(false);
const animationRef = useRef(new Animated.Value(0)).current;
const loadData = () => {
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
}, 3000)
}
useEffect(() => {
const rotateElement = () => {
animationRef.setValue(0);
Animated.timing(animationRef, {
toValue: 1,
duration: 1500,
easing: Easing.linear,
useNativeDriver: true,
}).start(rotateElement);
};
if (isLoading) {
rotateElement();
} else {
Animated.timing(animationRef).stop();
}
}, [isLoading, animationRef]);
const spin = animationRef.interpolate({
inputRange: [0, 1],
outputRange: ['0deg', '360deg'],
});
Snack:
https://snack.expo.dev/#wastelandtime/timer
Only try to remove the parameter rotateElement from start.
const rotateElement = () => {
animationRef.setValue(0);
Animated.timing(animationRef, {
toValue: 1,
duration: 1500,
easing: Easing.linear,
useNativeDriver: true,
}).start();
You could set a really long duration, ideally as long as your configured timeout. Then, you wouldn't need to call the start function passing the rotateElement as callback as Ivan suggested. That way, when you call the stop method, it will work as desired.
Another option would be using the react-native-reanimated package for writing this function in a more declarative way, making sure that the animation would run as long as you need it to.
I edited your snack, implementing the reanimated alternative: https://snack.expo.dev/#scalfs/timer
On seeing the code sample, simply using Animated.loop (with the required iterations) should get this working well enough. So the useEffect in the codeblock would be like so,
useEffect(() => {
const rotateElement = () => {
// animationRef.setValue(0); This isn't necessary anymore.
Animated.loop(
Animated.timing(animationRef, {
toValue: 1,
duration: 1500,
easing: Easing.linear,
useNativeDriver: true,
}),
{
iterations: 2,
}
).start(rotateElement);
};
if (isLoading) {
rotateElement();
} else {
Animated.timing(animationRef).stop();
}
}, [isLoading, animationRef]);
As for your use case, the iterations could be set to a high enough number and just use the if statement to stop the loop when loading is complete as you have already done here. You could also add a check for errors as well, so the loop doesn't continue ad infinitum.
You are creating a new instant of rotateElement each time your state changes. The animation you attempt to stop isn't the one you think.
const loadData = () => {
setIsLoading(true);
setTimeout(() => {
setIsLoading(false);
}, 1000)
}
const rotateElement = Animated.timing(animationRef, {
toValue: 1,
duration: 3000,
easing: Easing.linear,
useNativeDriver: true,
});
useEffect(() => {
if (isLoading) {
rotateElement.start();
} else {
rotateElement.stop();
}
}, [isLoading]);
Best creating the rotateElement outside the useEffect as shown above.
I've also changed the timing of the animation because the demo was too short to simulate end of loading correctly.

How to stop loop animation immediately in React Native

I am trying to achieve loop animation in react native. I am able to stop the animation as well, but it stops only after finishing the running animation in progress. Is there a way to stop it immediately. Since I am changing state value , I don't think I can use Animated.loop.
Edit: I tried stopping animation using
Animated.timing(this.state.opacity).stop()
But this had no effect on animation, it just kept going. So I added a boolean to control it, which works, but only after finishing the ongoing loop of animation.
Here is my code
this.state = {
varyingText: 'location',
stopAnimation: false,
opacity: new Animated.Value(0),
};
componentDidMount = () => {
this.startAnimation();
};
startAnimation = () => {
if (this.state.stopAnimation) return;
Animated.timing(this.state.opacity, {
toValue: 1,
duration: 1000
}).start(() => {
Animated.timing(this.state.opacity, {
toValue: 0,
duration: 1000
}).start(() => {
this.setState({
varyingText: 'location 1',
}, () => {
Animated.timing(this.state.opacity, {
toValue: 1,
duration: 1000
}).start(() => {
Animated.timing(this.state.opacity, {
toValue: 0,
duration: 1000
}).start(() => {
this.setState({
varyingText: 'location 2',
}, () => {
Animated.timing(this.state.opacity, {
toValue: 1,
duration: 1000
}).start(() => {
Animated.timing(this.state.opacity, {
toValue: 0,
duration: 1000
}).start(() => {
this.setState({
varyingText: 'location 3',
}, () => this.startAnimation())
})
})
})
})
})
})
})
});
};
stopAnimation = () => {
this.setState({
stopAnimation: true,
});
};
render() {
const opacityStyle = {
opacity: this.state.opacity,
};
return (
<View style={styles.view}>
<Animated.Text style={...opacityStyle}>
{this.state.varyingText}
</Animated.Text>
<Button
onPress={this.stopAnimation}
title="Stop"
color="#841584"
/>
</View>
);
}
In the code, if I stop the animation when text is location 1, it stops but only after showing location 2.

Toggle React-Native animation value

I try to toggle React-Native animation value, but my Animated.View is not animated, my translationX is "brute", without transition.
const OffCanvas = ({ visible, close }) => {
const WINDOW_WIDTH = Dimensions.get('window').width;
const animatedValue = new Animated.Value(visible ? 0 : WINDOW_WIDTH);
Animated.timing(animatedValue, {
toValue: visible ? 0 : WINDOW_WIDTH,
duration: 250,
easing: Easing.elastic(0.7),
delay: 0
}).start();
...
Anyone can help me ?
I think you try to animated your animatedValue up to its original value.
You probably need to put :
const animatedValue = new Animated.Value(visible ? 0 : WINDOW_WIDTH);
in componentDidMount() or in constructor() and store this value on your state.
Define WINDOW_WIDTH on top.
Add a listener to keep track of animated value inside the constructor.
Sample code
const WINDOW_WIDTH = Dimensions.get('window').width;
export default class Calls extends Component {
constructor() {
super();
this.animatedValue = new Animated.Value(0);
this.animatedValue.addListener(({ value }) => this._value = value);
}
toggle=() => {
const offset = JSON.parse(JSON.stringify(this.animatedValue));
Animated.timing(this.animatedValue, {
toValue: offset > WINDOW_WIDTH ? 0 : WINDOW_WIDTH,
duration: 250,
easing: Easing.elastic(0.7),
}).start();
}
....
===================================================
Or use a state variable to handle toggling.
Sample code
const WINDOW_WIDTH = Dimensions.get('window').width;
export default class Calls extends Component {
state = { toggle: false };
toggle=() => {
const { toggle } = this.state;
Animated.timing(this.animatedValue, {
toValue: toggle ? 0 : WINDOW_WIDTH,
duration: 250,
easing: Easing.elastic(0.7),
}).start(() => {
this.setState({ toggle: !toggle });
});
}

React native animation is not working for the second time

I have used the default "Animated" package with react native for my animations in the application. Animations in the following code is working fine. But when I navigate to another page and come back to this screen the animation is not working. Once the page gets loaded from ground level only it is working again. What could be the reason ? Can someone please help me to sort this out.
class LoginScreen extends Component {
static navigationOptions = {
header: null
}
state = {
username: '',
password: '',
animation: {
usernamePostionLeft: new Animated.Value(795),
passwordPositionLeft: new Animated.Value(905),
loginPositionTop: new Animated.Value(1402),
statusPositionTop: new Animated.Value(1542)
}
}
navigateToScreen = link => event => {
this.props.navigation.navigate(link)
}
componentDidMount() {
const timing = Animated.timing
Animated.parallel([
timing(this.state.animation.usernamePostionLeft, {
toValue: 0,
duration: 1700
}),
timing(this.state.animation.passwordPositionLeft, {
toValue: 0,
duration: 900
}),
timing(this.state.animation.loginPositionTop, {
toValue: 0,
duration: 700
}),
timing(this.state.animation.statusPositionTop, {
toValue: 0,
duration: 700
})
]).start()
}
render() {
return (
<View style={styles.container}>
<ImageBackground
source={lem_bg}
blurRadius={10}
style={styles.imageBgContainer}>
<View style={styles.internalContainer}>
<Animated.View style={{position: 'relative', top:
this.state.animation.usernamePostionLeft, width: '100%'}}>
<Text style={styles.LEMHeader}>LEM<Text style={styles.followingtext}>mobile</Text></Text>
</Animated.View>
</ImageBackground>
</View>
....MORE JSX ARE THERE...
)
}
}
componentDidMount() won't call when you navigate back from another screen. for this, you have to create your own callback method for performing this animation when you pop() from another screen. Consider below code change
first screen
navigateToScreen = link => event => {
this.props.navigation.navigate(link,{
callback:this.runAnimation
})
}
componentDidMount() {
this.runAnimation()
}
runAnimation(){
const timing = Animated.timing
Animated.parallel([
timing(this.state.animation.usernamePostionLeft, {
toValue: 0,
duration: 1700
}),
timing(this.state.animation.passwordPositionLeft, {
toValue: 0,
duration: 900
}),
timing(this.state.animation.loginPositionTop, {
toValue: 0,
duration: 700
}),
timing(this.state.animation.statusPositionTop, {
toValue: 0,
duration: 700
})
]).start()
}
on the second screen when you pop() navigation to back, call this callback
this.props.navigator.pop()
this.props.callback()

How to add a function call to Animated.sequence in react native

I have a button at the middle of my screen. onScroll I want the button to scale down to 0 to disappear and then scale back up to 1 to reappear in a new position at the bottom of the screen. I want to be able call setState (which controls the position of the button) in between the scale down and scale up animations. Something like the code below. Any idea of the best way to add a function call in between these two animations? Or an even better way of doing this?
animateScale = () => {
return (
Animated.sequence([
Animated.timing(
this.state.scale,
{
toValue: 0,
duration: 300
}
),
this.setState({ positionBottom: true }),
Animated.timing(
this.state.scale,
{
toValue: 1,
duration: 300
}
)
]).start()
)
}
After more research I found the answer.start() takes a callback function as shown here:
Calling function after Animate.spring has finished
Here was my final solution:
export default class MyAnimatedScreen extends PureComponent {
state = {
scale: new Animated.Value(1),
positionUp: true,
animating: false,
};
animationStep = (toValue, callback) => () =>
Animated.timing(this.state.scale, {
toValue,
duration: 200,
}).start(callback);
beginAnimation = (value) => {
if (this.state.animating) return;
this.setState(
{ animating: true },
this.animationStep(0, () => {
this.setState(
{ positionUp: value, animating: false },
this.animationStep(1)
);
})
);
};
handleScrollAnim = ({ nativeEvent }) => {
const { y } = nativeEvent.contentOffset;
if (y < 10) {
if (!this.state.positionUp) {
this.beginAnimation(true);
}
} else if (this.state.positionUp) {
this.beginAnimation(false);
}
};
render() {
return (
<View>
<Animated.View
style={[
styles.buttonWrapper,
{ transform: [{ scale: this.state.scale }] },
this.state.positionUp
? styles.buttonAlignTop
: styles.buttonAlignBottom,
]}
>
<ButtonCircle />
</Animated.View>
<ScrollView onScroll={this.handleScrollAnim}>
// scroll stuff here
</ScrollView>
</View>
);
}
}
That is correct answer.
Tested on Android react-native#0.63.2
Animated.sequence([
Animated.timing(someParam, {...}),
{
start: cb => {
//Do something
console.log(`I'm wored!!!`)
cb({ finished: true })
}
},
Animated.timing(someOtherParam, {...}),
]).start();