In my react native app I want to have a progressbar animated values, problem is when component is mounted the bar is full already (View with backgroundColor RED is visible) even though progressValue is 0 according to my console logs... if I execute onStart nothing moves since it appears like animation already finished...
Is it because I'm using translate wrongly? How can I achieve correct animated effect?
const width = Math.round(Dimensions.get('window').width)-30;
const progressValue = new Animated.Value(0);
//const animation = null;
const onStart = (duration ) =>
{
console.log(duration,width);
const animation = Animated.timing(progressValue, {
duration: duration,
toValue: width,
easing: Easing.linear,
useNativeDriver: true,
}).start();
};
<View style={{width: width, height: 4,backgroundColor: 'white'}}>
<Animated.View style={{width: width, height: 4, backgroundColor: 'red', transform: [{translateX: progressValue}]}} />
</View>
two things which i can see, always useRef for animation values
const width = Math.round(Dimensions.get('window').width)-30;
const progressValue = useRef(new Animated.Value(0)).current; // add this
//const animation = null;
const onStart = useCallback((duration ) =>
{
console.log(duration,width);
const animation = Animated.timing(progressValue, {
duration: duration,
toValue: width,
easing: Easing.linear,
useNativeDriver: true,
}).start();
},[progressValue]);
<View style={{width: width, height: 4,backgroundColor: 'white'}}>
<Animated.View style={{width: width, height: 4, backgroundColor: 'red', transform: [{translateX: progressValue}]}} />
</View>
Also im hoping youre calling onStart in a useEffect like this
useEffect(() => {
onStart()
},[onStart])
Hope it helps. feel free for doubts
Related
I have a progress bar screen inside navigation that I need to be reset every time user clicks on that specific route. Now it only plays animation when I go to that route for the first time. My question is: how to reset barWidth so that animation would play every time user clicks on specific route?
What have I tried?
I thought that the problem is that the component is not re-rendering and thats why value is not resetting, but it looks like the problem is that the width of the bar doesn't reset when user clicks on the screen after animation plays.
At first I've tried using useRef hook and later changed to simply setting the animated value to 0, but in both cases the bar's width didn't reset.
Code:
const { width } = Dimensions.get("screen");
const SettingsScreen = ({navigation}) => {
const isFocused = useIsFocused();
return (
<FlatList
contentContainerStyle={style.barContainer}
data={[1, 2, 3, 4, 5]}
keyExtractor={(_, index) => index.toString()}
renderItem={() => (
<ProgressBar isFocused={isFocused} navigation={navigation} />
)}
></FlatList>
);
};
const ProgressBar = ({ navigation, isFocused }) => {
//const barWidth = React.useRef(new Animated.Value(0)).current;
const barWidth = new Animated.Value(0);
console.log(barWidth);
const finalWidth = width / 2;
React.useEffect(() => {
const listener = navigation.addListener("focus", () => {
Animated.spring(barWidth, {
toValue: finalWidth,
bounciness: 10,
speed: 2,
useNativeDriver: false,
}).start();
});
return listener;
}, [navigation]);
return (
<View style={style.contentContainer}>
<Animated.View style={[style.progressBar, { width: barWidth }]} />
</View>
);
};
const style = StyleSheet.create({
contentContainer: {
flex: 1,
justifyContent: "center",
padding: 30,
},
barContainer: {
padding: 30,
},
progressBar: {
backgroundColor: "green",
width: width / 2,
height: 15,
borderRadius: 15,
},
});
The addListener function is Deprecated. try to use addEventListener instead.
also, why is it inside a const listener with the return listener?
as i see it you can write the useEffect like that:
React.useEffect(() => {
navigation.addEventListener("focus", () => {
Animated.spring(barWidth, {
toValue: finalWidth,
bounciness: 10,
speed: 2,
useNativeDriver: false,
}).start();
});
}, [navigation]);
I am new to React Native animated, and I am trying to incorporate it into my app. I currently have an object that appears to fall from the middle to the bottom of the screen, using frequent state updates like this:
const [objHeight, setObjHeight] = useState((Dimensions.get("screen").height)/2)
useEffect(()=>{
if(objHeight > 0){
timerId = setInterval(()=>{
setObjHeight(objHeight => objHeight - 3)
}, 30) //30ms
return ()=>{
clearInterval(timerId)
}
},[objHeight])
//then the object looks like:
<View
style = {[{
position: "absolute",
backgroundColor: 'blue',
width: 50,
height: 60,
bottom: objHeight,
}]}>
</View>
I understand that this is an inefficient way to do animations on react, and that by using react animated we can make animate this on the UI thread instead. I have tried to replicate the above using react native animated.
const objHeight = new Animated.Value(screenHeight/2)
Animated.loop(
Animated.timing(objHeight, {
toValue: objHeight>0 ? objHeight - gravity : null,
duration: 3000,
useNativeDriver: true
}),
{iterations: 1000}
).start()
<Animated.View
style = {[{
backgroundColor: 'blue',
width: 50,
height: 60,
bottom: 200,
transform:[{translateY: objHeight}]
}]}>
</Animated.View>
However, the object does not move/animate. It just stays at the same height. What am I doing wrong? I find the documentation on react native animated is not particularly helpful.
Thank you
Snack Link
First create an Animated.Value with some initial value:
const bottomAnim = useRef(new Animated.Value(Dimensions.get('screen').height / 2)).current;
Then on component mount start the animation:
useEffect(() => {
Animated.timing(bottomAnim, {
toValue: 0,
duration: 3000,
useNativeDriver: true,
}).start();
}, []);
Finally, bind the animated value to the component:
return (
<Animated.View
style={[
{
position: 'absolute',
backgroundColor: 'blue',
width: 50,
height: 60,
bottom: bottomAnim,
},
]}/>
);
I am trying to use React Native Animated to do simple animation with View width and Height.
Following the official syntax from here React Animated. But the stop functionality is not working.
Here is my code snippet:
import React, { useRef } from "react";
import { Animated, View, StyleSheet, Button } from "react-native";
const App = () => {
const sizeAnimBase = new Animated.Value(100);
const sizeAnim = useRef(sizeAnimBase).current;
const startAnimation = () => {
Animated.timing(sizeAnim, {
toValue: 500,
duration: 100,
useNativeDriver: false
}).start(() => {
});
}
const stopAnimation = () => {
// Tried both ways but didn't work
Animated.timing(sizeAnimBase).stop();
Animated.timing(sizeAnim).stop();
}
return (
<View>
<Animated.View
style={
width: sizeAnim,
height: sizeAnim
}
>
<Text>Animated view</Text>
</Animated.View>
<Button title="Start Animation" onPress={startAnimation} />
<Button title="Stop Animation" onPress={stopAnimation} />
</View>
)
}
This worked for me normally
const App = () => {
const sizeAnim = useRef(new Animated.Value(100)).current;
const startAnimation = () => {
Animated.timing(sizeAnim, {
toValue: 500,
duration: 2000,
useNativeDriver: false,
}).start();
};
const stopAnimation = () => {
Animated.timing(sizeAnim, {
duration: 0,
toValue: 0,
useNativeDriver: true,
}).stop();
};
return (
<View>
<Animated.View style={{width: sizeAnim, height: sizeAnim}}>
<Text>Animated view</Text>
</Animated.View>
<Button title="Start Animation" onPress={startAnimation} />
<Button title="Stop Animation" onPress={stopAnimation} />
</View>
);
}
How are you testing whether it's working or not? You've set duration to 100ms, its practically impossible to press Stop Animation button before the animation completes.
Increase the duration to something like 5000ms and you'll set it's working perfectly.
Here is a snack link.
This is a React Native app. I'm implementing "cover" component that displays an image, once video is loaded, image fades out and video is playing instead. Here is my code:
export default function Cover() {
const [playVideo, setPlayVideo] = useState(false);
const [coverOpacityAnimation] = useState(new Animated.Value(1))
useEffect(() => {
if (!playVideo) return;
Animated.timing(coverOpacityAnimation, {
toValue: 0,
duration: 1000
}).start()
}, [playVideo])
return (
<View>
<Animated.View style={{opacity: coverOpacityAnimation}}>
<Image source={{uri: data.cover}} />
</Animated.View>
<Video
source={{uri: 'https://videostorage.net/public/video.mp4'}}
onReadyForDisplay={() => setPlayVideo(true)} />
</View>
);
}
The issue that I'm having is, when setPayVideo(true) is called from Video component fading out is not happening, image just disappears and video is playing. If I do:
useEffect(() => {
Animated.timing(coverOpacityAnimation, {
toValue: 0,
duration: 1000
}).start()
})
Animation works as expected. I need to trigger it from Video onReadyForDisplay. New to hooks - please help :)
Let's say I have a view that is positioned absolute at the bottom of the screen. This view contains a text input. When the text input is focused, I want the bottom of the view to touch the top of the keyboard.
I've been messing around with KeyboardAvoidingView, but the keyboard keeps going over my view. Is it not possible to make this work with position absolute?
What other method can I try? Thanks!
Few days ago I have the same problem (although I have a complex view with TextInput as a child) and wanted not only the TextInput to be focused but the whole view to be "attached" to the keyboard. What's finally is working for me is the following code:
constructor(props) {
super(props);
this.paddingInput = new Animated.Value(0);
}
componentWillMount() {
this.keyboardWillShowSub = Keyboard.addListener('keyboardWillShow', this.keyboardWillShow);
this.keyboardWillHideSub = Keyboard.addListener('keyboardWillHide', this.keyboardWillHide);
}
componentWillUnmount() {
this.keyboardWillShowSub.remove();
this.keyboardWillHideSub.remove();
}
keyboardWillShow = (event) => {
Animated.timing(this.paddingInput, {
duration: event.duration,
toValue: 60,
}).start();
};
keyboardWillHide = (event) => {
Animated.timing(this.paddingInput, {
duration: event.duration,
toValue: 0,
}).start();
};
render() {
return (
<KeyboardAvoidingView behavior='padding' style={{ flex: 1 }}>
[...]
<Animated.View style={{ marginBottom: this.paddingInput }}>
<TextTranslateInput />
</Animated.View>
</KeyboardAvoidingView>
);
}
where [..] you have other views.
Custom hook:
import { useRef, useEffect } from 'react';
import { Animated, Keyboard, KeyboardEvent } from 'react-native';
export const useKeyboardHeight = () => {
const keyboardHeight = useRef(new Animated.Value(0)).current;
useEffect(() => {
const keyboardWillShow = (e: KeyboardEvent) => {
Animated.timing(keyboardHeight, {
duration: e.duration,
toValue: e.endCoordinates.height,
useNativeDriver: true,
}).start();
};
const keyboardWillHide = (e: KeyboardEvent) => {
Animated.timing(keyboardHeight, {
duration: e.duration,
toValue: 0,
useNativeDriver: true,
}).start();
};
const keyboardWillShowSub = Keyboard.addListener(
'keyboardWillShow',
keyboardWillShow
);
const keyboardWillHideSub = Keyboard.addListener(
'keyboardWillHide',
keyboardWillHide
);
return () => {
keyboardWillHideSub.remove();
keyboardWillShowSub.remove();
};
}, [keyboardHeight]);
return keyboardHeight;
};
#jazzdle example works great! Thank you for that!
Just one addition - in keyboardWillShow method, one can add event.endCoordinates.height so paddingBottom is exact height as keyboard.
keyboardWillShow = (event) => {
Animated.timing(this.paddingInput, {
duration: event.duration,
toValue: event.endCoordinates.height,
}).start();
}
Using Functional Component. This works for both iOS and Android
useEffect(() => {
const keyboardVisibleListener = Keyboard.addListener(
Platform.OS === "ios" ? "keyboardWillShow" : "keyboardDidShow",
handleKeyboardVisible
);
const keyboardHiddenListener = Keyboard.addListener(
Platform.OS === "ios" ? "keyboardWillHide" : "keyboardDidHide",
handleKeyboardHidden
);
return () => {
keyboardHiddenListener.remove();
keyboardVisibleListener.remove();
};}, []);
const handleKeyboardVisible = (event) => {
Animated.timing(paddingInput, {
duration: event.duration,
toValue: 60,
useNativeDriver: false,
});};
const handleKeyboardHidden = (event: any) => {
Animated.timing(paddingInput, {
duration: event.duration,
toValue: 0,
useNativeDriver: false,
});};
React Native now supports an InputAccessoryView which can be used for exactly this purpose - even for anchored TextInputs.
Here's a specific example: https://github.com/facebook/react-native/blob/main/packages/rn-tester/js/examples/InputAccessoryView/InputAccessoryViewExample.js
You can use flexbox to bottom position the element. Here's simple example -
render() {
return (
<View style={styles.container}>
<View style={styles.top}/>
<View style={styles.bottom}>
<View style={styles.input}>
<TextInput/>
</View>
</View>
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
top: {
flex: .8,
},
bottom: {
flex: .2,
},
input: {
width: 200,
},
});