multiple react native animations with reanimated base on a state? - react-native

I am new to react native animations and I am specifically using react native reanimated, this is my code old code which is used in text input, I am changing the fontsize and translate Y of a label:
const labelSize = new Animated.Value(14);
const translateY = new Animated.Value(0);
const onFocus = () => {
setHasFocused(true);
Animated.timing(labelSize, {
toValue: 11,
duration: 250,
easing: Easing.in(Easing.ease),
}).start();
Animated.timing(top, {
toValue: -14,
duration: 250,
easing: Easing.in(Easing.ease),
}).start();
};
const onBlur = () => {
setHasFocused(false);
Animated.timing(labelSize, {
toValue: 14,
duration: 250,
easing: Easing.out(Easing.ease),
}).start();
Animated.timing(top, {
toValue: 0,
duration: 250,
easing: Easing.in(Easing.ease),
}).start();
};
however it doesn't seem to feel right, I've researched and resulted to this, I believe I have to use clock and timing with this, which I find hard and can't make to work, what I want to do is something like this:
const clock = new Clock();
useCode(
() => [
set(isPlaying, hasFocused ? 1 : 0),
set(translateY, hasFocused ? 1 : 0),
],
[hasFocused]
);
now I believe with timing I have to use clock with it:
const state = {
finished: new Animated.Value(0),
position: new Animated.Value(0),
frameTime: new Animated.Value(0),
time: new Animated.Value(0),
};
const config = {
toValue: new Animated.Value(1),
duration: 3000,
easing: Easing.inOut(Easing.ease),
};
timing(clock, state, config)
what I simply want to do is update animation values based if the input is focused and not, help?

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.

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 animation is no working with setState function

I have this function:
const moveSwitch = () => {
setLeftStatus(!leftStatus);
Animated.timing(xValue, {
toValue: leftStatus ? 0 : 26,
duration: 300,
easing: Easing.linear,
useNativeDriver: false,
}).start();
};
It works if I remove the conditional option and setState.
Any suggestions please?
If anyone comes around this problem here is how I solved it.
Since setState function re-renders component animation was not working.
So I just created ref using useRef and updated it like this:
const moveSwitch = () => {
Animated.timing(xValue, {
toValue: leftStatus.current ? 0 : 26,
duration: 300,
easing: Easing.linear,
useNativeDriver: false,
}).start(event => {
if (event.finished) {
if (leftStatus.current) {
return (leftStatus.current = false);
} else {
return (leftStatus.current = true);
}
}
});
};

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

How to remove latency of UI when setting state of big array in React Native?

I am using React Native to render a Chart.
I have 3 custom tabs in my app, when sliding between them I am getting new data and apply translation animation to highlight the selected tab.
I am facing a 1 second latency when scrolling. I investigated the latency and there is one parameter in setState() that is causing it, which is a big array of data.
I am already using async / await.
I tried to put setState() inside a setTimeout but the final result isn't smooth.
Is there a way to optimize setting big array in the state ? Or play the animation inpendently from setState() ?
Update
await Animated.spring(translateBottomX, {
toValue: type,
duration: 100,
}).start();
if (segment === 1) {
await Animated.parallel([
Animated.spring(translateXBottomTabOne, {
toValue: 0,
duration: 100,
}).start(),
Animated.spring(translateXBottomTabTwo, {
toValue: width,
duration: 100,
}).start(),
]);
} else if (segment === 2) {
await Animated.parallel([
Animated.spring(translateXBottomTabOne, {
toValue: width,
duration: 100,
}).start(),
Animated.spring(translateXBottomTabTwo, {
toValue: 0,
duration: 100,
}).start(),
]);
} else if (segment === 3) {
await Animated.parallel([
Animated.spring(translateXBottomTabOne, {
toValue: width,
duration: 0,
}).start(),
Animated.spring(translateXBottomTabTwo, {
toValue: width,
duration: 100,
}).start(),
]);
}
setTimeout(() => {
this.setState({
dataAxeX
});
}, 500);
// using it here
<XAxis
data={dataAxeX}
formatLabel={(_, index) => dataAxeX[index].Datetime}
contentInset={styles.xAxisContentInset}
svg={{
fill: "#B6C1DF",
...styles.xAxisStyle,
}}
/>