In this demo we'll walk through sequence of animating to 4 corners of the screen. We'll take advantage of Dimensions
, Animated.ValueXY
, Animated.sequence
, and Animated.spring
. We will gather the layout dynamically and animate the view to all the corners.
We'll setup a basic React Native component. We'll add 3 additional imports, Animated
, TouchableWithoutFeedback
, and Dimensions
.
We'll create a state variable called animation
to hold our Animated.ValueXY
.
import React, { Component } from "react"; import { AppRegistry, StyleSheet, Text, View, Animated, TouchableWithoutFeedback, Dimensions, } from "react-native"; export default class animations extends Component { state = { animation: new Animated.ValueXY(), }; render() { return ( <View style={styles.container}> <TouchableWithoutFeedback> <Animated.View style={[styles.box]} /> </TouchableWithoutFeedback> </View> ); } }
Our TouchableWithoutFeedback
will use the React.cloneElement
command and apply properties to our child view without adding any additional layout items. This means that we should apply our box styling to the Animated.View
. This is different than other Touchable
elements as they usually wrap the child element in an Animated.View
which will effect positioning.
const styles = StyleSheet.create({ container: { flex: 1, }, box: { width: 150, height: 150, backgroundColor: "tomato", position: "absolute", top: 0, left: 0, }, });
A key concept in animating some complex animations is having to know the current dimensions of a particular element and or a target element. There are a few methods but one of the most basic is using the onLayout
prop. Due to the layout calculations occurring on the native side, we need to pass a callback so we can get the values once the view layout completes.
We will also need to pass them to our TouchableWithoutFeedback
because of the way it is structured to use React.cloneElement
and it explicitly overrides the onLayout
of the child view. So if we passed it to our Animated.View
it would never be called.
<TouchableWithoutFeedback onPress={this.startAnimation} onLayout={this.saveDimensions} >
Here we simply save off the width and height so we can access it later.
saveDimensions = (e) => { this._width = e.nativeEvent.layout.width; this._height = e.nativeEvent.layout.height; };
Now we'll need to also know the height and width of the window, so we'll use our Dimensions
import and get the width
and height
of our window.
For our first corner animation, we'll go down the left side of the screen. Meaning we'll need to animate the y
value of our Animated.ValueXY
. We need to animate to the height
of the screen minus the size of our box
which we put at this._height
.
startAnimation = () => { const { width, height } = Dimensions.get("window"); Animated.spring(this.state.animation.y, { toValue: height - this._height, }); };
The next corner will be the bottom right. Which means we need to animate across our screen. We already animated our y
position so now we just need to animate our x
position. Just like height we'll need to use our screen width
minus the box
width we saved at this._width
.
Animated.spring(this.state.animation.x, { toValue: width - this._width, });
Now we're in the bottom right, and we'll move our box back up to the top right. We don't need to know the height as we are just animating our y
back to the beginning which is 0
.
Animated.spring(this.state.animation.y, { toValue: 0, });
Finally we animate our x
back to 0
where it started and we're officially at the start.
Animated.spring(this.state.animation.x, { toValue: 0, });
Now to combine all of our animations to happen one after the other we can simply use Animated.sequence
and pass the entire series of animations in as an array.
startAnimation = () => { const { width, height } = Dimensions.get("window"); Animated.sequence([ Animated.spring(this.state.animation.y, { toValue: height - this._height, }), Animated.spring(this.state.animation.x, { toValue: width - this._width, }), Animated.spring(this.state.animation.y, { toValue: 0, }), Animated.spring(this.state.animation.x, { toValue: 0, }), ]).start(); };
We need to apply our style, and Animated.ValueXY
comes with a handy transform shorthand. The getTranslateTransform
will return an array of transforms for translateX
and translateY
.
The equivalent would be
[ { translateX: this.state.animation.x }, { translateY: this.state.animation.y }, ];
render() { const animatedStyles = { transform: this.state.animation.getTranslateTransform() } return ( <View style={styles.container}> <TouchableWithoutFeedback onPress={this.startAnimation} onLayout={this.saveDimensions} > <Animated.View style={[styles.box, animatedStyles]} /> </TouchableWithoutFeedback> </View> ); }
This demo is not complex by any means, the purpose is to focus on how we can use the Dimensions of the screen and dynamically measure the view so we can move it around the screen. The animation doesn't have to be completed all at the same time either. Using sequence and other animation combinators like parallel and stagger we can progressively move items around using the same animated value. This is a crucial concept to understand especially when it comes to dealing with interpolations.