React native animation is not working for the second time - react-native

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()

Related

React Native Animation Boomerang style

I am trying to create a red led "Recording Like" element. I'd like to see it blinking smoothly.
useEffect(() => {
Animated.loop(
Animated.timing(
fadeAnim,
{
toValue: 0,
duration: 1000
}
).start(() =>{
Animated.timing(
fadeAnim,
{
toValue: 1,
duration: 1000
}
).start();
})
).start();
}, [fadeAnim])
<Animated.View style={[styles.recordingLed, {opacity: fadeAnim}]} />
It actually goes to 0, then to 1 and then stop. I need something continuos, endless. Any clue ?
You need to add some Easing (https://reactnative.dev/docs/easing) to your animation run smoothly, example:
const fadeAnim = new Animated.Value(0);
useEffect(() => {
Animated.loop(
Animated.timing(fadeAnim, {
toValue: 1,
duration: 1000,
easing: Easing.bezier(0.645, 0.045, 0.355, 1.0),
useNativeDriver: true,
}),
).start();
}, [fadeAnim]);
<Animated.View style={[styles.recordingLed, {opacity: fadeAnim}]} />
Also you can check this:
Maybe some pre made animations from can help you https://github.com/oblador/react-native-animatable#animations-2 ? Remember to mark the looping flag (https://github.com/oblador/react-native-animatable#looping)

React native shaking pin view when pin incorrect

i am trying to implement a feature when user enters the wrong password the 4 small circles shake to the right and left.
I created an animation component to test my code and it was working but now my problem is how do i apply it only when the password is incorrect?
Currently when i enter an incorrect password nothing happens. What i expect is for the view to move horizontally.
const shake = new Animated.Value(0.5);
const [subtle,setSubtle]=useState(true);
const [auto,setAuto]=useState(false);
const translateXAnim = shake.interpolate({
inputRange: [0, 1],
outputRange: [subtle ? -8 : -16, subtle ? 8 : 16],
});
const getAnimationStyles = () => ({
transform: [
{
translateX: translateXAnim,
},
],
});
const runAnimation = () => {
Animated.sequence([
Animated.timing(shake, {
delay: 300,
toValue: 1,
duration: subtle ? 300 : 200,
easing: Easing.out(Easing.sin),
useNativeDriver: true,
}),
Animated.timing(shake, {
toValue: 0,
duration: subtle ? 200 : 100,
easing: Easing.out(Easing.sin),
useNativeDriver: true,
}),
Animated.timing(shake, {
toValue: 1,
duration: subtle ? 200 : 100,
easing: Easing.out(Easing.sin),
useNativeDriver: true,
}),
Animated.timing(shake, {
toValue: 0,
duration: subtle ? 200 : 100,
easing: Easing.out(Easing.sin),
useNativeDriver: true,
}),
Animated.timing(shake, {
toValue: 0.5,
duration: subtle ? 300 : 200,
easing: Easing.out(Easing.sin),
useNativeDriver: true,
}),
]).start(() => {
if (auto) runAnimation();
});
};
const stopAnimation = () => {
shake.stopAnimation();
};
const handleConfirm = async()=>{
const result = await authApi();
if(!result.ok) {
setAuto(true)
setSubtle(true)
runAnimation()
stopAnimation()
return setLoginFailed(true);
}
setLoginFailed(false);
};
return(
<Animated.View style={[getAnimationStyles()]}>
<View style={styles.circleBlock}>
{
password.map(p=>{
let style =p != ''?styles.circleFill
: styles.circle
return <View style={style}></View>
})
}
</View>
</Animated.View>
issue is that you didn't use useRef() for
const shake = new Animated.Value(0.5);
should be
const shake = useRef(new Animated.Value(0.5)).current;
useRef returns a mutable ref object whose .current property is
initialized to the passed argument (initialValue). The returned object
will persist for the full lifetime of the component.
https://reactjs.org/docs/hooks-reference.html#useref
made separate expo snack with same input and output range values for animation shake effect. check it out.
Expo: https://snack.expo.io/#klakshman318/runshakeanimation

React Native spawn multiple Animated.View with a time delay

I have a Wave component that unmounts as soon as it goes out of bounds.
This is my result so far: https://streamable.com/hmjo6k
I would like to have multiple Waves spawn every 300ms, I've tried implementing this using setInterval inside useEffect, but nothing behaves like expected and app crashes
const Wave = ({ initialDiameter, onOutOfBounds }) => {
const scaleFactor = useRef(new Animated.Value(1)).current,
opacity = useRef(new Animated.Value(1)).current
useEffect(() => {
Animated.parallel(
[
Animated.timing(scaleFactor, {
toValue: 8,
duration: DURATION,
useNativeDriver: true
}),
Animated.timing(opacity, {
toValue: 0.3,
duration: DURATION,
useNativeDriver: true
})
]
).start(onOutOfBounds)
})
return (
<Animated.View
style={
{
opacity,
backgroundColor: 'white',
width: initialDiameter,
height: initialDiameter,
borderRadius: initialDiameter/2,
transform: [{scale: scaleFactor}]
}
}
/>
)
}
export default Wave
const INITIAL_WAVES_STATE = [
{ active: true },
]
const Waves = () => {
const [waves, setWaves] = useState(INITIAL_WAVES_STATE),
/* When out of bounds, a Wave will set itself to inactive
and a new one is registered in the state.
Instead, I'd like to spawn a new Wave every 500ms */
onWaveOutOfBounds = index => () => {
let newState = waves.slice()
newState[index].active = false
newState.push({ active: true })
setWaves(newState)
}
return (
<View style={style}>
{
waves.map(({ active }, index) => {
if (active) return (
<Wave
key={index}
initialDiameter={BUTTON_DIAMETER}
onOutOfBounds={onWaveOutOfBounds(index)}
/>
)
else return null
})
}
</View>
)
}
export default Waves
I think the useEffect cause the app crashed. Try to add dependencies for the useEffect
useEffect(() => {
Animated.parallel(
[
Animated.timing(scaleFactor, {
toValue: 8,
duration: DURATION,
useNativeDriver: true
}),
Animated.timing(opacity, {
toValue: 0.3,
duration: DURATION,
useNativeDriver: true
})
]
).start(onOutOfBounds)
}, [])

Looping a react native animated animation

I am attempting to put the following animation in an infinite loop until a particular state occurs:
class MyModal extends Component {
constructor() {
super()
this.springValue = new Animated.Value(0.3)
}
spring = () => {
this.springValue.setValue(0.3)
Animated.spring(
this.springValue,
{
toValue: 1,
friction: 1,
tension: 1,
duration:5000
}
).start()
}
componentDidMount() {
this.spring()
}
render() {
return (
<View>
<Modal
animationType="none"
transparent={false}
visible={this.state.modalVisible}
onRequestClose={() => null}
>
<View style={styles.backgroundStyle}>
<Animated.Image
source={require("./my-awesome-image.png")}
style={{ width: 143, height: 125, top: 100, transform: [{scale: this.springValue}]}}
/>
</View>
</Modal>
</View>
);
}
}
Everything here works great, the animation completes once (as I'm not looping anywhere).
How do I keep my Animated.Image looping until I reach a particular state? I just want it infinite looping and the ability to either stop the animation or start another animation when I'm ready to.
Store your animation in a variable you can access and just wrap your animation with Animated.loop(). Then you can freely use .start() and .stop() on that variable holding the animation as you please.
So something like this should do:
this.springAnimation = Animated.loop(
Animated.spring(
this.springValue,
{
toValue: 1,
friction: 1,
tension: 1,
duration:5000
}
).start()
)
You can find more information about that here:
https://facebook.github.io/react-native/docs/animated.html#loop
Pass a callback to the start function to check whether to restart the animation. You could break it down to something like this:
onSpringCompletion = () => {
if (arbitraryCondition) {
this.spring();
}
}
spring = () => {
this.springValue.setValue(0.3)
Animated.spring(
this.springValue,
{
toValue: 1,
friction: 1,
tension: 1,
duration:5000
}
).start(this.onSpringCompletion);
}
You can use setInterval to keep the animation playing and remove the interval whenever you want. I would do this:
componentDidMount() {
this.interval = setInterval(() => this.spring(), 5000) // Set this time higher than your animation duration
}
...
// Some where in your code that changes the state
clearInterval(this.interval)
...

Animate listview items when they are added/removed from datasource

Can someone give me an idea of how this can be done, e.g. animate the height from 0 when added and back to 0 when removed?
Animation when added is easy, just use Animated in componentDidMount with your listRow , for example:
componentDidMount = ()=> {
Animated.timing(this.state._rowOpacity, {
toValue: 1,
duration: 250,
}).start()
}
Animate a component before unmount is much harder in react-native. You should set a handler for ListView. When dataSource changed, diff the data, start Animated to hide removed row, and set new dataSource for ListView.
Here you can get full working example for opacity animation:
import React from 'react-native';
export default class Cell extends React.Component {
constructor(props) {
super(props);
this.state = {
opacity: new React.Animated.Value(0)
};
}
componentDidMount() {
React.Animated.timing(this.state.opacity, {
toValue: 1,
duration: 250,
}).start();
}
render() {
return (
<React.Animated.View style={[styles.wrapper, {opacity: this.state.opacity}]}>
<React.Image source={{uri: 'http://placehold.it/150x150'}} style={styles.image}/>
<React.Text style={styles.text}>
Text
</React.Text>
</React.Animated.View>
);
}
}
const styles = React.StyleSheet.create({
wrapper: {
flex: 1,
flexDirection: 'row',
justifyContent: 'flex-start',
alignItems: 'center',
},
image: {
height: 40,
width: 40,
marginRight: 16,
backgroundColor: '#C9D5E6'
},
text: {
fontSize: 20
}
});
In case you need for removing an item from the list, here's how to do the ListRow component:
class DynamicListRow extends Component {
// these values will need to be fixed either within the component or sent through props
_defaultHeightValue = 60;
_defaultTransition = 500;
state = {
_rowHeight : new Animated.Value(this._defaultHeightValue),
_rowOpacity : new Animated.Value(0)
};
componentDidMount() {
Animated.timing(this.state._rowOpacity, {
toValue : 1,
duration : this._defaultTransition
}).start()
}
componentWillReceiveProps(nextProps) {
if (nextProps.remove) {
this.onRemoving(nextProps.onRemoving);
} else {
// we need this for iOS because iOS does not reset list row style properties
this.resetHeight()
}
}
onRemoving(callback) {
Animated.timing(this.state._rowHeight, {
toValue : 0,
duration : this._defaultTransition
}).start(callback);
}
resetHeight() {
Animated.timing(this.state._rowHeight, {
toValue : this._defaultHeightValue,
duration : 0
}).start();
}
render() {
return (
<Animated.View
style={{height: this.state._rowHeight, opacity: this.state._rowOpacity}}>
{this.props.children}
</Animated.View>
);
}
}
i've posted a complete tutorial to this question in this blog post. And it's explaining step by step what you need to do to accomplish both adding and removing an item and animate this process.
For adding is pretty straight forward, but for removing looks like it's a little bit more complex.
http://moduscreate.com/react-native-dynamic-animated-lists/
Here's a full example for height and opacity animation. It supports both adding and removing an element. The key point is that you need to reset the height and opacity after the disappearing animation completes. Then you immediately delete the item from the source.
export const ListItem = (props: ListItemProps) => {
// Start the opacity at 0
const [fadeAnim] = useState(new Animated.Value(0));
// Start the height at 0
const [heightAnim] = useState(new Animated.Value(0));
/**
* Helper function for animating the item
* #param appear - whether the animation should cause the item to appear or disappear
* #param delay - how long the animation should last (ms)
* #param callback - callback to be called when the animation finishes
*/
const _animateItem = (appear: boolean = true, delay: number = 300, callback: () => void = () => null) => {
Animated.parallel([
Animated.timing(
fadeAnim,
{
toValue: appear ? 1 : 0,
duration: delay,
}
),
Animated.timing(
heightAnim,
{
toValue: appear ? 100 : 0,
duration: delay,
}
),
]).start(callback);
};
// Animate the appearance of the item appearing the first time it loads
// Empty array in useEffect results in this only occuring on the first render
React.useEffect(() => {
_animateItem();
}, []);
// Reset an item to its original height and opacity
// Takes a callback to be called once the reset finishes
// The reset will take 0 seconds and then immediately call the callback.
const _reset = (callback: () => void) => {
_animateItem(true,0, callback);
}
// Deletes an item from the list. Follows the following order:
// 1) Animate the item disappearing. On completion:
// 2) Reset the item to its original display height (in 0 seconds). On completion:
// 3) Call the parent to let it know to remove the item from the list
const _delete = () => {
_animateItem(false, 200, () => _reset(props.delete));
};
return (
<Animated.View
style={{height: heightAnim, opacity: fadeAnim, flexDirection: 'row'}}>
<Text>{props.text}</Text>
<Button onPress={() => _delete()}><Text>Delete</Text></Button>
</Animated.View>
);
}