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.
Related
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
I am using react native with expo cli and I have a component:
import React, {useEffect, useState} from 'react'
import {View, TextInput, Text, TouchableOpacity, Animated, Easing} from 'react-native';
import s from './Login_style'
import {connect} from "react-redux";
const LoginInner = (props) => {
const [mode, setMode] = useState(true)
const btnAnim = new Animated.Value(0)
const setModeAnim = (is) => {
if (is) {
Animated.timing(btnAnim, {
toValue: 1,
duration: 300,
easing: Easing.out(Easing.exp),
useNativeDriver: false,
}).start()
// setMode(false)
} else {
Animated.timing(btnAnim, {
toValue: 0,
duration: 300,
easing: Easing.out(Easing.exp),
useNativeDriver: false
}).start()
// setMode(true)
}
}
const size1 = btnAnim.interpolate({
inputRange: [0, 1],
outputRange: ['30%', '60%']
})
const size2 = btnAnim.interpolate({
inputRange: [0, 1],
outputRange: ['60%', '30%']
})
return (
<View style={s.page}>
<View style={s.inputs}>
<Text style={[s.text, s.title]}>{mode ? 'Вход' : 'Регистрация'}</Text>
<View style={s.textInput}>
<View>
<Text style={[s.text, s.placeholder]}>Логин</Text>
</View>
<TextInput style={s.input}/>
</View>
<View style={s.textInput}>
<View>
<Text style={[s.text, s.placeholder]}>Пароль</Text>
</View>
<TextInput style={s.input}/>
</View>
<View style={s.actions}>
<Animated.View style={[s.btn, s.loginBtn, {width: size2}]}>
<TouchableOpacity style={s.touchableBtn} onPress={() => {
setModeAnim(false)
}}>
<Text style={s.loginBtn_text}>{mode ? 'Вход' : '<--'}</Text>
</TouchableOpacity>
</Animated.View>
<Animated.View style={[s.btn, s.regBtn, {width: size1}]}>
<TouchableOpacity style={s.touchableBtn} onPress={() => {
setModeAnim(true)
}}>
<Text style={s.loginBtn_text}>{mode ? '-->' : 'Регистрация'}</Text>
</TouchableOpacity>
</Animated.View>
</View>
</View>
</View>
)
}
const Login = connect((state) => {
return {}
}, {})(LoginInner)
export default Login
Here I am interested in this function
const setModeAnim = (is) => {
if (is) {
Animated.timing(btnAnim, {
toValue: 1,
duration: 300,
easing: Easing.out(Easing.exp),
useNativeDriver: false,
}).start()
// setMode(false)
} else {
Animated.timing(btnAnim, {
toValue: 0,
duration: 300,
easing: Easing.out(Easing.exp),
useNativeDriver: false
}).start()
// setMode(true)
}
}
I run the animation here when the function is executed (ignore the commented out line for now). The animation really works.
VIDEO DEMONSTRATION: https://youtu.be/fiGy0gNej68
But if I change the state of the component in this function after starting the animation:
setMode(false) or setMode(true) animation does not start, even the buttons do not change their size
VIDEO DEMONSTRATION: https://youtu.be/deLZEKVnaBY
Tell me how to solve this
Thanks
To add to #LIMPIX64's answer:
using const btnAnim = React.useRef(new Animated.Value(0)).current solves the problem because since Animated.Value is an instance of a class, you need to wrap it in a useRef call so that it only gets created once when the component renders for the first time (which is what useRef accomplishes). Otherwise, the class could be reinstantiated on a state change and cause the issue you're experiencing
Please do const btnAnim = React.useRef(new Animated.Value(0)).current – Harrison
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 :)
I want to animate this image carousel in reactNative, but have no idea how to start. Read the documentation about animations but still really stuck, have no idea how to incorporate it in. I tried it this way but keep getting a big fat error. Help!
import React from 'react';
import {StyleSheet, View, ScrollView, Dimensions, Image, Animated} from 'react-native'
const DEVICE_WIDTH = Dimensions.get('window').width;
class BackgroundCarousel extends React.Component {
scrollRef = React.createRef();
constructor(props) {
super(props);
this.state = {
selectedIndex: 0,
opacity: new Animated.Value(0)
};
}
componentDidMount = () => {
Animated.timing(this.state.opacity , {
toValue: 1,
duration: 500,
useNativeDriver: true,
}).start();
setInterval(() => {
this.setState(
prev => ({ selectedIndex: prev.selectedIndex ===
this.props.images.length - 1 ? 0 : prev.selectedIndex +1 }),
() => {
this.scrollRef.current.scrollTo({
animated: true,
y: 0,
x: DEVICE_WIDTH * this.state.selectedIndex
});
}
);
}, 6000);
};
componentWillUnmount() {
clearInterval(this.setState);
}
render() {
const {images} = this.props
const {selectedIndex} = this.state
return (
<Animated.Image
onLoad={this.onLoad}
{...this.props}
style={[
{
opacity: this.state.opacity,
},
this.props.style,
]}
/>
<View style= {{height: "100%", width: "100%"}}>
{this.props.children}
<ScrollView
horizontal
pagingEnabled
scrollEnabled={false}
ref={this.scrollRef}
>
{images.map(image => (
<Image
key={image}
source={image}
style={styles.backgroundImage}
/>
))}
</ScrollView>
</View>
)
}
}
const styles = StyleSheet.create ({
backgroundImage: {
height: '100%',
width: DEVICE_WIDTH,
}
});
export default BackgroundCarousel;
Any help would be appreciated. Don't know where I'm going wrong. Basically trying to add a fade effect when my background carousel changes from image to image.
I have fixed your code and removed all errors, copy-paste it in https://snack.expo.io/ and give it some time to load.
Note: I have removed this.props.images for website demo, please change in your real project.
Working fade carousal: https://snack.expo.io/#rajrohityadav/fade-carosal
But I have not implemented this using React Animation.
import React from 'react';
import {StyleSheet, View, ScrollView, Dimensions, Image, Animated} from 'react-native'
const DEVICE_WIDTH = Dimensions.get('window').width;
export default class BackgroundCarousel extends React.Component {
scrollRef = React.createRef();
constructor(props) {
super(props);
this.state = {
selectedIndex: 0,
opacity: new Animated.Value(0)
};
}
componentDidMount = () => {
Animated.timing(this.state.opacity , {
toValue: 1,
duration: 500,
useNativeDriver: true,
}).start();
setInterval(() => {
this.setState(
prev => ({ selectedIndex: prev.selectedIndex ===
3 - 1 ? 0 : prev.selectedIndex +1 }),
() => {
this.scrollRef.current.scrollTo({
animated: true,
y: 0,
x: DEVICE_WIDTH * this.state.selectedIndex
});
}
);
}, 6000);
};
componentWillUnmount() {
clearInterval(this.setState);
}
render() {
const images =[
'https://image.shutterstock.com/image-vector/dragon-scream-vector-illustration-tshirt-260nw-1410107855.jpg','https://image.shutterstock.com/image-vector/dragon-head-vector-illustration-mascot-600w-1201914655.jpg',
'https://i.pinimg.com/474x/b7/1a/bb/b71abb6dd7678bbd14a1f56be5291747--dragon-illustration-samurai-tattoo.jpg']//this.props
const {selectedIndex} = this.state
return (
<>
<Animated.Image
onLoad={this.onLoad}
{...this.props}
style={[
{
opacity: this.state.opacity,
},
this.props.style,
]}
/>
<View style= {{height: "100%", width: "100%"}}>
{this.props.children}
<ScrollView
horizontal
pagingEnabled
scrollEnabled={false}
ref={this.scrollRef}
>
{images.map(image => (
<Image
key={image}
source={image}
style={styles.backgroundImage}
/>
))}
</ScrollView>
</View>
</>
)
}
}
const styles = StyleSheet.create ({
backgroundImage: {
height: '100%',
width: DEVICE_WIDTH,
}
});
You can also use a simple & optimized library react-native-fadecarousel and use it like this:
import React from 'react'
import { View, StyleSheet, Image } from 'react-native';
import FadeCarousel from 'react-native-fadecarousel';
const FadeCarouselScreen = () => {
const images = [
'https://image.shutterstock.com/image-vector/dragon-scream-vector-illustration-tshirt-260nw-1410107855.jpg',
'https://image.shutterstock.com/image-vector/dragon-head-vector-illustration-mascot-600w-1201914655.jpg',
'https://i.pinimg.com/474x/b7/1a/bb/b71abb6dd7678bbd14a1f56be5291747--dragon-illustration-samurai-tattoo.jpg'
];
return <FadeCarousel
loop
fadeAnimationDuration={1000}
autoPlay={{enable: true , delay: 1000 }}>
{
images.map((image, index) => {
return <View key={`slide ${index}`} style={styles.slideItem}>
<Image style={styles.image} resizeMethod="resize" resizeMode="cover" source={{ uri: image }}/>
</View>
})
}
</FadeCarousel>
}
const styles = StyleSheet.create({
image: {
width: "100%",
height: 300
},
slideItem: {
width: "100%",
height: 300,
justifyContent: "center",
alignContent: "center"
}
})
export default FadeCarouselScreen
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,
},
});