Related
I'm using a swipe animation, the same one that is being used in apps like tinder.
The animation used to work in a Class component and in JSX but I had to change it to a function with hooks in TSX for a project. Now the animation Doensn't work anymore but it also doesn't give a error. I guess I'm missing some code that is necessary to let it work or it also can be something with the Panresponder. But I absolutely got no clue.
Thanks in advance.
const SCREEN_HEIGHT = Dimensions.get('window').height
const SCREEN_WIDTH = Dimensions.get('window').width
const { width, height } = Dimensions.get('screen')
const thumbMeasure = (width - 48 - 32) / 3
const MyComponent = (props: { index: any }) => {
const [imagesData, setImagesData] = useState<Images[]>([])
console.clear()
console.log('Images data:', imagesData)
useEffect(() => {
LogBox.ignoreLogs(['Animated: `useNativeDriver`']);
}, [])
useEffect(() => {
axios
.get<Images[]>('https://api.thecatapi.com/v1/images/search?breed_ids=beng&include_breeds=true')
.then((response: AxiosResponse) => {
setImagesData(response.data)
})
}, [])
const position = new Animated.ValueXY()
const [index, setIndex] = useState(props.index || 0)
const [panResponder, setPanResponder] = useState<any>()
const rotate = position.x.interpolate({
inputRange: [-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2],
outputRange: ['-30deg', '0deg', '10deg'],
extrapolate: 'clamp',
})
const rotateAndTranslate = {
transform: [{
rotate: rotate
},
...position.getTranslateTransform()
]
}
const likeOpacity = position.x.interpolate({
inputRange: [-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2],
outputRange: [0, 0, 1],
extrapolate: 'clamp',
})
const dislikeOpacity = position.x.interpolate({
inputRange: [-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2],
outputRange: [1, 0, 0],
extrapolate: 'clamp',
})
const nextCardOpacity = position.x.interpolate({
inputRange: [-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2],
outputRange: [1, 0, 1],
extrapolate: 'clamp',
})
const nextCardScale = position.x.interpolate({
inputRange: [-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2],
outputRange: [1, 0.8, 1],
extrapolate: 'clamp',
})
useEffect(() => {
setPanResponder( PanResponder.create({
onStartShouldSetPanResponder: (_evt, _gestureState) => true,
onPanResponderMove: (_evt, gestureState) => {
position.setValue({ x: gestureState.dx, y: gestureState.dy })
},
onPanResponderRelease: (_evt, gestureState) => {
if (gestureState.dx > 120) {
Animated.spring(position, {
toValue: { x: SCREEN_WIDTH + 100, y: gestureState.dy },
useNativeDriver: true,
}).start(() => {
setIndex( () => {
position.setValue({ x: 0, y: 0 })
return index + 1
})
})
} else if (gestureState.dx < -120) {
Animated.spring(position, {
toValue: { x: -SCREEN_WIDTH - 100, y: gestureState.dy },
useNativeDriver: true,
}).start(() => {
setIndex( () => {
position.setValue({ x: 0, y: 0 })
return index + 1
})
})
} else {
Animated.spring(position, {
toValue: { x: 0, y: 0 },
friction: 4, useNativeDriver: true,
}).start()
}
},
}))
}, [])
const Users = () => {
return imagesData.map((item, i) => {
if (i < index) {
return null
} else if (i == index) {
return (
<Animated.View
{...panResponder.panhandlers}
key={item.id}
style={[
rotateAndTranslate,
{
height: SCREEN_HEIGHT - 120,
width: SCREEN_WIDTH,
padding: 10,
position: 'absolute',
},
]}
>
<Animated.View
style={{
opacity: likeOpacity,
transform: [{ rotate: '-30deg' }],
position: 'absolute',
top: 50,
left: 40,
zIndex: 1000,
}}
>
<Text
style={{
borderWidth: 1,
borderColor: 'green',
color: 'green',
fontSize: 32,
fontWeight: '800',
padding: 10,
}}
>
LIKE
</Text>
</Animated.View>
<Animated.View
style={{
opacity: dislikeOpacity,
transform: [{ rotate: '30deg' }],
position: 'absolute',
top: 50,
right: 40,
zIndex: 1000,
}}
>
<Text
style={{
borderWidth: 1,
borderColor: 'red',
color: 'red',
fontSize: 32,
fontWeight: '800',
padding: 10,
}}
>
NOPE
</Text>
</Animated.View>
<Image
style={{ height: '86%', width: null, borderRadius: 10 }}
source={{ uri: `${item.url}` }}
/>
<View style={{backgroundColor: '', color: 'black', fontSize: 20, fontWeight: '800', position: 'absolute', bottom: 95, right: 40, zIndex: 1000, width: '85%', height: '20%', borderRadiusTop: 20, }}>
<Text style={{ color: 'white', fontSize: 32, fontWeight: '800' }}>
{item.breeds[0].name}
</Text>
<Text style={{ color: 'white', fontSize: 18, fontWeight: '600' }}>
{item.breeds[0].origin}
</Text>
<View style={styles.iconen}>
<Ionicons style={styles.icon} name="timer" />
<Text style={styles.subtitle}>{item.breeds[0].life_span}</Text>
<Ionicons style={styles.icon} name="barbell-outline" />
<Text style={styles.subtitle}>{item.breeds[0].weight.metric}</Text>
<Ionicons style={styles.icon} name="earth" />
<Text style={styles.subtitle}>{item.breeds[0].country_code}</Text>
</View>
</View>
</Animated.View>
)
} else {
return (
<Animated.View
key={item.id}
style={[
{
opacity: nextCardOpacity,
transform: [{ scale: nextCardScale }],
height: SCREEN_HEIGHT - 120,
width: SCREEN_WIDTH,
padding: 10,
position: 'absolute',
},
]}
>
<Animated.View
style={{
opacity: 0,
transform: [{ rotate: '-30deg' }],
position: 'absolute',
top: 50,
left: 40,
zIndex: 1000,
}}
>
<Text
style={{
borderWidth: 1,
borderColor: 'green',
color: 'green',
fontSize: 32,
fontWeight: '800',
padding: 10,
}}
>
LIKE
</Text>
</Animated.View>
<Animated.View
style={{
opacity: 0,
transform: [{ rotate: '30deg' }],
position: 'absolute',
top: 50,
right: 40,
zIndex: 1000,
}}
>
<Text
style={{
borderWidth: 1,
borderColor: 'red',
color: 'red',
fontSize: 32,
fontWeight: '800',
padding: 10,
}}
>
NOPE
</Text>
</Animated.View>
<Image
style={{ height: '86%', width: null, borderRadius: 10 }}
source={{ uri: `${item.url}` }}
/>
</Animated.View>
)
}
})
.reverse()
}
return (
<View>
<View>
{Users()}
</View>
<View style={{ height: SCREEN_HEIGHT - 80 }}>
<ButtonVote />
</View>
</View>
)
}
I am trying to rotate the view but it's working well if I didn't set the borderRadius. But It's not working well if I set the borderRadius.
Here is a code
inner: {
width: '100%',
height: '100%',
borderRadius: 1,
borderWidth: 5,
borderStyle: 'solid',
borderColor: 'rgba(0, 255, 170, 0.3)',
borderRightWidth: 0,
borderTopWidth: 0,
}
const value = useRef(new Animated.Value(0)).current;
const rotate = () => {
value.setValue(0);
Animated.timing(value, {
toValue: 1,
duration: 2000,
useNativeDriver: true,
}).start(() => {
rotate();
});
};
useEffect(() => {
rotate();
}, [rotate]);
<Animated.View
style={[
{width: '100%', height: '100%', backgroundColor: 'transparent'},
{
transform: [
{
rotate: '15deg',
},
],
},
]}>
<View style={[styles.inner]} />
</Animated.View>
[
I'm using Axios to retrieve my data from my API in this Class component in TSX.
It doesn't give any error, it just doesn't show my Images or not even the data in a console.log. I'm now using a class component with tsx because in my previous attempts to let a animated slider work I used: Class component in jsx, Class component in tsx, Function in JSX with hooks aswell in TSX. At every attempt there were different errors that I couldn't solve that's why I'm using this strategy now and I guess I'm close now to the sollution, it's just the Data that's not viewing.
Thanks in advance
class MyComponent extends Component {
componentDidMount() {
LogBox.ignoreLogs(['Animated: `useNativeDriver`'])
}
position
rotate: number
rotateAndTranslate
likeOpacity
dislikeOpacity
nextCardOpacity
nextCardScale
PanResponder
imagesData: Images[]
mySpecialFunction() {
console.log('Images data:', this.imagesData)
}
constructor(props) {
super(props)
LogBox.ignoreLogs(['Animated: `useNativeDriver`'])
axios
.get<Images[]>(
'https://api.thecatapi.com/v1/images/search?breed_ids=beng&include_breeds=true',
)
.then((response: AxiosResponse) => {
this.imagesData = response.data
})
this.position = new Animated.ValueXY()
this.state = {
currentIndex: 0,
}
this.rotate = this.position.x.interpolate({
inputRange: [-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2],
outputRange: ['-30deg', '0deg', '10deg'],
extrapolate: 'clamp',
})
this.rotateAndTranslate = {
transform: [
{
rotate: this.rotate,
},
...this.position.getTranslateTransform(),
],
}
this.likeOpacity = this.position.x.interpolate({
inputRange: [-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2],
outputRange: [0, 0, 1],
extrapolate: 'clamp',
})
this.dislikeOpacity = this.position.x.interpolate({
inputRange: [-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2],
outputRange: [1, 0, 0],
extrapolate: 'clamp',
})
this.nextCardOpacity = this.position.x.interpolate({
inputRange: [-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2],
outputRange: [1, 0, 1],
extrapolate: 'clamp',
})
this.nextCardScale = this.position.x.interpolate({
inputRange: [-SCREEN_WIDTH / 2, 0, SCREEN_WIDTH / 2],
outputRange: [1, 0.8, 1],
extrapolate: 'clamp',
})
}
UNSAFE_componentWillMount() {
this.PanResponder = PanResponder.create({
onStartShouldSetPanResponder: (evt, gestureState) => true,
onPanResponderMove: (evt, gestureState) => {
this.position.setValue({ x: gestureState.dx, y: gestureState.dy })
},
onPanResponderRelease: (evt, gestureState) => {
if (gestureState.dx > 120) {
Animated.spring(this.position, {
toValue: { x: SCREEN_WIDTH + 100, y: gestureState.dy },
useNativedriver: true,
}).start(() => {
this.setState({ currentIndex: this.state.currentIndex + 1 }, () => {
this.position.setValue({ x: 0, y: 0 })
})
})
} else if (gestureState.dx < -120) {
Animated.spring(this.position, {
toValue: { x: -SCREEN_WIDTH - 100, y: gestureState.dy },
useNativedriver: true,
}).start(() => {
this.setState({ currentIndex: this.state.currentIndex + 1 }, () => {
this.position.setValue({ x: 0, y: 0 })
})
})
} else {
Animated.spring(this.position, {
toValue: { x: 0, y: 0 },
friction: 4,
useNativedriver: true,
}).start()
}
},
})
}
renderUsers = () => {
if (this.imagesData) {
return this.imagesData
.map((item, i) => {
if (i < this.state.currentIndex) {
return null
} else if (i == this.state.currentIndex) {
return (
<Animated.View
{...this.PanResponder.panHandlers}
key={item.id}
style={[
this.rotateAndTranslate,
{
height: SCREEN_HEIGHT - 120,
width: SCREEN_WIDTH,
padding: 10,
position: 'absolute',
},
]}
>
<Animated.View
style={{
opacity: this.likeOpacity,
transform: [{ rotate: '-30deg' }],
position: 'absolute',
top: 50,
left: 40,
zIndex: 1000,
}}
>
<Text
style={{
borderWidth: 1,
borderColor: 'green',
color: 'green',
fontSize: 32,
fontWeight: '800',
padding: 10,
}}
>
LIKE
</Text>
</Animated.View>
<Animated.View
style={{
opacity: this.dislikeOpacity,
transform: [{ rotate: '30deg' }],
position: 'absolute',
top: 50,
right: 40,
zIndex: 1000,
}}
>
<Text
style={{
borderWidth: 1,
borderColor: 'red',
color: 'red',
fontSize: 32,
fontWeight: '800',
padding: 10,
}}
>
NOPE
</Text>
</Animated.View>
<Image
style={{ height: '86%', width: null, borderRadius: 10 }}
source={{ uri: `${item.url}` }}
/>
<View
style={{
backgroundColor: '',
color: 'black',
fontSize: 20,
fontWeight: '800',
position: 'absolute',
bottom: 95,
right: 40,
zIndex: 1000,
width: '85%',
height: '20%',
borderRadiusTop: 20,
}}
>
<Text
style={{ color: 'white', fontSize: 32, fontWeight: '800' }}
>
Black cat
</Text>
<Text
style={{ color: 'white', fontSize: 18, fontWeight: '600' }}
>
Black cat family
</Text>
<View style={styles.iconen}>
<Ionicons style={styles.icon} name="timer" />
<Text style={styles.subtitle}>
{item.breeds[0].life_span}
</Text>
<Ionicons style={styles.icon} name="barbell-outline" />
<Text style={styles.subtitle}>
{item.breeds[0].weight.metric}
</Text>
<Ionicons style={styles.icon} name="earth" />
<Text style={styles.subtitle}>
{item.breeds[0].country_code}
</Text>
</View>
</View>
</Animated.View>
)
} else {
return (
<Animated.View
key={item.id}
style={[
{
opacity: this.nextCardOpacity,
transform: [{ scale: this.nextCardScale }],
height: SCREEN_HEIGHT - 120,
width: SCREEN_WIDTH,
padding: 10,
position: 'absolute',
},
]}
>
<Animated.View
style={{
opacity: 0,
transform: [{ rotate: '-30deg' }],
position: 'absolute',
top: 50,
left: 40,
zIndex: 1000,
}}
>
<Text
style={{
borderWidth: 1,
borderColor: 'green',
color: 'green',
fontSize: 32,
fontWeight: '800',
padding: 10,
}}
>
LIKE
</Text>
</Animated.View>
<Animated.View
style={{
opacity: 0,
transform: [{ rotate: '30deg' }],
position: 'absolute',
top: 50,
right: 40,
zIndex: 1000,
}}
>
<Text
style={{
borderWidth: 1,
borderColor: 'red',
color: 'red',
fontSize: 32,
fontWeight: '800',
padding: 10,
}}
>
NOPE
</Text>
</Animated.View>
<Image
style={{ height: '86%', width: null, borderRadius: 10 }}
source={{ uri: `${item.url}` }}
/>
</Animated.View>
)
}
})
.reverse()
}
}
render() {
return (
<View>
<View>{this.renderUsers()}</View>
<View style={{ height: SCREEN_HEIGHT - 80 }}>
<ButtonVote />
</View>
</View>
)
}
}
Try this way by using state for reflecting changes after API respond
class MyComponent extends Component {
state = {
imagesData: [],
};
// rest of the code remains the same
componentDidMount() {
axios.get(`xxxxxx`).then((res) => {
const imagesData = res.data;
this.setState({ imagesData });
});
}
renderUsers = () => {
if (this.state.imagesData) {
return this.state.imagesData.map((item, i) => {}).reverse();
}
};
render() {
return <View></View>;
}
}
I want to implement animation in my react native app. I have created the animation but facing difficulty to manage these according to my requirement.
I want to show animation one by one.
Here is the video link for animation currently showing in my app :-
https://www.dropbox.com/s/jzwqpbkp6fqi8qw/SLXG5627.MP4?dl=0
In this video both animation working parallel, so I want to manage them one by one, like first animation complete then second will run.
Here is the code that I have written for that :-
constructor(props) {
super(props)
this.animatedValue = new Animated.Value(0);
this.animatedValue1 = new Animated.Value(3);
this.animatedValue2 = new Animated.Value(300);
this.state = {
userData: getUserDetail(),
vouch_height: new Animated.Value(30),
vouch_width: new Animated.Value(30),
startSecond:false,
startFirst:true,
startFirst:false
}
}
animation() {
this.animatedValue.setValue(0)
this.animatedValue1.setValue(3)
this.animatedValue2.setValue(300)
Animated.sequence([
Animated.parallel([
Animated.timing(
this.animatedValue,
{
toValue: 2,
duration: 2500,
useNativeDriver: false
}
),
Animated.timing(this.animatedValue1, {
toValue: 0.3,
duration: 2500,
useNativeDriver: false
}),
Animated.timing(this.animatedValue2, {
toValue: 50,
duration: 2500,
useNativeDriver: false
})
])
]).start()
}
animation1() {
this.state.vouch_height.setValue(30)
this.state.vouch_width.setValue(30)
Animated.sequence([
Animated.parallel([
Animated.timing(
this.state.vouch_width, // The animated value to drive
{
toValue: 50, // Animate to opacity: 1 (opaque)
duration: 150, // Make it take a while
useNativeDriver: false,
},
), // Starts the animation
Animated.timing(
this.state.vouch_height, // The animated value to drive
{
toValue: 50, // Animate to opacity: 1 (opaque)
duration: 150, // Make it take a while
useNativeDriver: false,
},
)
]),
Animated.parallel([
Animated.timing(
this.state.vouch_width, // The animated value to drive
{
toValue: 30, // Animate to opacity: 1 (opaque)
duration: 150, // Make it take a while
useNativeDriver: false,
},
), // Starts the animation
Animated.timing(
this.state.vouch_height, // The animated value to drive
{
toValue: 30, // Animate to opacity: 1 (opaque)
duration: 150, // Make it take a while
useNativeDriver: false,
},
),
])
]).start()
}
Here is the render method :-
render() {
const { thumbnailSource, source, style, ...props } = this.props;
return (
<Modal
animationIn='fadeIn'
animationOut='fadeOut'
style={styles.modalContent}
isVisible={this.props.isVisible}
flex={1}
justifyContent='center'
alignItems='center'
backdropOpacity={0.0}
hideModalContentWhileAnimating={true}
>{this.props.isVisible ? <>
{this.animation()}
<View style={{ top: 50, height: this.props.viewHeight + 50, width: windowWidth, justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(255, 255, 255, 0.8)' }}>
<Animated.View
useNativeDriver={true}
style={{
height: 300,
width: 300,
backgroundColor: "white",
justifyContent: "center",
alignItems: "center",
borderRadius: 150,
opacity: this.animatedValue1,
transform: [
{
translateY: this.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 250]
})
},
{
scaleX: this.animatedValue.interpolate({
inputRange: [0, 1, 2],
outputRange: [1, 0, 0],
})
},
{
scaleY: this.animatedValue.interpolate({
inputRange: [0, 1, 2],
outputRange: [1, 0, 0],
})
},
],
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 9,
},
shadowOpacity: 0.9,
shadowRadius: 40.35,
elevation: 19,
}}
>
<FastImage style={[
styles.imageStyle,
]}
source={this.props.img}
resizeMode='cover' />
</Animated.View>
</View>
{this.animation1()}
<Animated.View style={{
position: 'absolute', top: global.vouchIconPossision.y,
width: this.state.vouch_width,
height: this.state.vouch_height,
margin: 0,
}}>
<global.UserImg />
</Animated.View>
</> : null}
</Modal >
)
}
}
What I am doing wrong, can anyone please suggest me ?
I think you need to remove Animated.parallel, and use just Animated.sequence
Animated.sequence([
Animated.timing(
...
),
Animated.timing(
...
),
]).start()
call 2nd animation in "start()" as callback.
Change first method like this.
animation(callbackAnimation) {
this.animatedValue.setValue(0)
this.animatedValue1.setValue(3)
this.animatedValue2.setValue(300)
Animated.sequence([
Animated.parallel([
Animated.timing(
this.animatedValue,
{
toValue: 2,
duration: 2500,
useNativeDriver: false
}
),
Animated.timing(this.animatedValue1, {
toValue: 0.3,
duration: 2500,
useNativeDriver: false
}),
Animated.timing(this.animatedValue2, {
toValue: 50,
duration: 2500,
useNativeDriver: false
})
])
]).start(callbackAnimation())
}
Then call it like this
animation(animation1);
i am trying to create animated header where the height, fontSize and position are being changed while scrolling like header of uber
it works but it is not smooth
when i scroll slowly it shakes very fast
constructor:
constructor(props) {
super(props);
this.state = {
fontSizeAnimation: new Animated.Value(30),
positionX: new Animated.Value(0),
positionY: new Animated.Value(0),
height: new Animated.Value(0),
positionAnimation: new Animated.ValueXY(),
scrollY: 0,
counter: 0,
}
}
function of animation:
animateTitle = (e) => {
const scrollY = e.nativeEvent.contentOffset.y;
if(scrollY - this.state.scrollY > 5 || scrollY - this.state.scrollY < -5) {
this.setState({counter: this.state.counter+1})
this.setState({scrollY});
Animated.parallel([
Animated.timing(this.state.height, {
toValue: this.state.scrollY,
duration: 0,
easing: Easing.linear
}),
Animated.timing(this.state.fontSizeAnimation, {
toValue: this.state.scrollY,
duration: 0,
easing: Easing.linear
})
]).start(() => {
this.state.positionAnimation.setValue({
x: this.state.scrollY > 50? 50 : this.state.scrollY,
y: this.state.scrollY > 50? -50 : -this.state.scrollY,
})
})
}
}
render function:
render() {
const interpolatedFontSize = this.state.fontSizeAnimation.interpolate({
inputRange: [0, 50],
outputRange: [30, 20],
extrapolate: "clamp"
});
const interpolatedHeight = this.state.height.interpolate({
inputRange: [0, 50],
outputRange: [120, 60],
extrapolate: "clamp"
});
return (
<View>
<Animated.View style={[styles.header, {height: interpolatedHeight}]}>
<Image source={require("./images/back-button.png")} style={styles.back} />
<Animated.Text style={[styles.title, { fontSize: interpolatedFontSize, transform: this.state.positionAnimation.getTranslateTransform() }]}>Title</Animated.Text>
</Animated.View>
<ScrollView
onScroll={(e) => this.animateTitle(e)}
contentContainerStyle={{minHeight: "100%", height: 1000, backgroundColor: "grey"}}
>
<View style={{height: 800, backgroundColor: "red", width: "100%", marginBottom: 10}}>
</View>
</ScrollView>
</View>
)
}
}
style:
const styles = StyleSheet.create({
back: {
position: "absolute",
top: 20,
left: 10,
height: 30,
width: 30
},
title: {
position: "absolute",
paddingTop: 5,
top: 60,
left: 10
},
header: {
position: "relative",
backgroundColor:"grey",
}
});
Your animations are currently running on JS thread. You can easily move the animations to the native thread by:
Animated.timing(this.state.animatedValue, {
toValue: 1,
duration: 500,
useNativeDriver: true, // <-- Adding this line
}).start();
Using native drivers can give you a performance boost as it clears the JS thread for other tasks. Try it!
You should use LayoutAnimation if you want to animate layouts smoothly (height in this case). Here's a link to get you started.