React Native tips — Animate cards (react-native-elements, interpolation and more)

Hi guys, in this post I will show you how to create a simple swipe effect with cards, Tinder like. I am going to use basically React Native components except the react-native-elements to create the Cards.
You can see how to setup the dev environment here: https://medium.com/@leonardobrunolima/react-native-tips-setting-up-your-development-environment-for-windows-d326635604ea?source=linkShare-42ccfccbb437-1535561576
TL;DR
You can clone the project and use this post as reference, but if you want read the full post go ahead!
Let’s get started creating a new project using react-native cli:
$ react-native init SwipeIt --version 0.55.4Add react-native-elements package:
$ npm i react-native-elementsAs we are not using EXPO, we have to install react-native-vector-icons and link it:
$ npm i --save react-native-vector-iconsLink it:
$ react-native link react-native-vector-iconsYou can check the documentation here: https://react-native-training.github.io/react-native-elements/docs/0.19.0/getting_started.html
Let’s separate development in steps:
1 — Create the folder src and the basic card container component:

//import liraries
import React, { Component } from 'react';
import { View } from 'react-native';// create a component
class CardsContainer extends Component {
renderCards() {
return this.props.data.map((item, index) => {
return (
<View key={item.id}>
{this.props.renderCard(item)}
</View>
)
});
} render() {
return (
<View>
{this.renderCards()}
</View>
);
}
}//make this component available to the app
export default CardsContainer;
Ok, this is the basic cards container, as you can see we have two props: this.props.data and this.props.renderCard. The render method just call the renderCards method and we call the renderCard for each item in data prop. These props we are going to pass from App.js component.
2 — Configure App.js to create and pass the props to CardsContainer component:
We are going to use react-native-elements to create the cards, you can check the documentation here: https://react-native-training.github.io/react-native-elements/docs/0.19.0/card.html
On cards we need to show some images, so I will get those images from unsplash.com and create a const DATA to hold this images. You can get it from anywhere you want. Let’s see the App.js code:
import React, { Component } from 'react';
import {
StyleSheet,
Text,
View
} from 'react-native';import { Card, Button } from 'react-native-elements';
import CardsContainer from './src/CardsContainer';const DATA = [
{ id: 1, text: 'Card #1', uri: 'https://images.unsplash.com/photo-1535591273668-578e31182c4f?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=f28261f0564880c9086a57ee87a68887&auto=format&fit=crop&w=500&q=60' },{ id: 2, text: 'Card #2', uri: 'https://images.unsplash.com/photo-1535576434247-e0f50b766399?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=232f6dbab45b3f3a6f97e638c27fded2&auto=format&fit=crop&w=500&q=60' },{ id: 3, text: 'Card #3', uri: 'https://images.unsplash.com/photo-1535565454739-863432ea3c0e?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=7edfb9bc7d214dbf2c920723cb0ffce2&auto=format&fit=crop&w=500&q=60' },{ id: 4, text: 'Card #4', uri: 'https://images.unsplash.com/photo-1535546204504-586398ee6677?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=7320b162b147a94d4c41377d9035e665&auto=format&fit=crop&w=500&q=60' },{ id: 5, text: 'Card #5', uri: 'https://images.unsplash.com/photo-1535531298052-7457bcdae809?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=f15acb2aedb30131bb287589399185a2&auto=format&fit=crop&w=500&q=60' },{ id: 6, text: 'Card #6', uri: 'https://images.unsplash.com/photo-1535463731090-e34f4b5098c5?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=ebe64b284c0ccffbac6a0d7ce2c8d15a&auto=format&fit=crop&w=500&q=60' },{ id: 7, text: 'Card #7', uri: 'https://images.unsplash.com/photo-1535540707939-6b4813adb681?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=ce3177d04728f7d1811e342b47d1e391&auto=format&fit=crop&w=500&q=60' },{ id: 8, text: 'Card #8', uri: 'https://images.unsplash.com/photo-1535486509975-18366f9825df?ixlib=rb-0.3.5&ixid=eyJhcHBfaWQiOjEyMDd9&s=ea59f63a657824d02872bb907fe85e76&auto=format&fit=crop&w=500&q=60' }
];export default class App extends Component {
renderCard(item) {
return (
<Card
key={item.id}
title={item.text}
image={{ uri: item.uri }}
>
<Text style={{ marginBottom: 10 }}>
Testing....
</Text>
<Button
backgroundColor='#03A9F4'
title="More details"
/>
</Card>
);
} render() {
return (
<View style={styles.container}>
<CardsContainer
data={DATA}
renderCard={this.renderCard}
/>
</View>
);
}
}const styles = StyleSheet.create({
container: {
flex: 1,
}
});
We basically have the the DATA and renderCard method to pass to CardsContainer component. Now let’s see how it looks like on emulator:
$ react-native run-android
Cool, now we can go to CardsContainer component and do the magic:
3 — Adjust on Cards position and add basic animation:
First, let’s make the cards aligned and create a basic translation animation. Add the Style and set the position to absolute, so all cards will be fixed on top. Once you do that, the width will follow lowest size inside the container, so we need to add the width manually using the screen width using Dimensions component:
//import liraries
import React, { Component } from 'react';
import {
View,
StyleSheet,
Dimensions
} from 'react-native';const SCREEN_WIDTH = Dimensions.get('window').width;// create a component
class CardsContainer extends Component {
renderCards() {
return this.props.data.map((item, index) => {
return (
<View
style={styles.cardStyle}
key={item.id}
>
{this.props.renderCard(item)}
</View>
)
}).reverse();
} render() {
return (
<View>
{this.renderCards()}
</View>
);
}
}// define your styles
const styles = StyleSheet.create({
cardStyle: {
position: 'absolute',
width: SCREEN_WIDTH
},
});//make this component available to the app
export default CardsContainer;
As you can see I called the reverse() method on renderCards otherwise the last image will be on top.
Now we must have something like this:

Animations!! Let’s go!
I am going to use Animated and PanResponder, the same components I’ve used in the post below, take some time, read it and come back:
The code is becoming big, don’t worry, you can clone it on GitHub: https://github.com/lblima/react-native-swipeit
//import liraries
import React, { Component } from 'react';
import {
View,
StyleSheet,
Dimensions,
Animated,
PanResponder
} from 'react-native';const SCREEN_WIDTH = Dimensions.get('window').width;// create a component
class CardsContainer extends Component {
constructor(props) {
super(props); const position = new Animated.ValueXY();
const panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: (event, gesture) => {
position.setValue({ x: gesture.dx, y: gesture.dy });
},
onPanResponderRelease: (event, gesture) => {}
}); this.state = { panResponder, position, index: 0 };
} renderCards() {
return this.props.data.map((item, index) => {
//Add animation only to the card on top
if (index === this.state.index) {
return (
<Animated.View
style={[
styles.cardStyle,
this.state.position.getLayout()
]}
{...this.state.panResponder.panHandlers}
key={item.id}
>
{this.props.renderCard(item)}
</Animated.View>
)
} return (
<Animated.View
style={styles.cardStyle}
key={item.id}
>
{this.props.renderCard(item)}
</Animated.View>
);
}).reverse();
} render() {
return (
<View>
{this.renderCards()}
</View>
);
}
}...export default CardsContainer;
This is the basic code to make an element move when you drag it on screen, now we must have this:
4 — Interpolation and rotation
The next step is rotate the card when you change the X axis, we can do it using interpolation. It is basically saying whenever you move some pixel on X axis do something (move Y axis, change color, rotate, etc). Let’s do that:
...//Add animation only to the card on top
if (index === this.state.index) {
return (
<Animated.View
style={[
styles.cardStyle,
this.state.position.getLayout()
]}
{...this.state.panResponder.panHandlers}
key={item.id}
>
{this.props.renderCard(item)}
</Animated.View>
)
}...
In this part of the code, we have the position been passed to Animated.View using the line: this.state.position.getLayout() and the position is changed on onPanResponderMove event. What we have to do now is modify this behavior to not only pass the new position, but do some rotation on card. So let’s create a new method to do this called getCardStyle.
...getCardStyle() {
const { position } = this.state;
const rotationX = SCREEN_WIDTH * 2; const rotate = position.x.interpolate({
inputRange: [-rotationX, 0, rotationX],
outputRange: ['-120deg', '0deg', '120deg']
}); return {
...position.getLayout(),
transform: [{ rotate }]
}
}renderCards() {
return this.props.data.map((item, index) => {
if (index < this.state.index)
return null;
//Add animation only to the card on top
if (index === this.state.index) {
return (
<Animated.View
style={[
styles.cardStyle,
this.getCardStyle()
]}
{...this.state.panResponder.panHandlers}
key={item.id}
>
{this.props.renderCard(item)}
</Animated.View>
);
}
return (
<Animated.View
key={item.id}
style={[
styles.cardStyle,
{transform: [{ rotate: '0deg'}]},
{ top: 10 * (index - this.state.index) }
]}
>
{this.props.renderCard(item)}
</Animated.View>
);
}).reverse();
}...
Now we are getting the style for the first card calling this method getCardStyle that apply the interpolation to the element. Whenever you move on X axis e have a rotation o same direction.
To the other cards, I had to apply a {transform: [{ rotate: ‘0deg’}]} due a bug for Android.
To make the UX more rich, I’ve added a small space to other cards: { top: 10 * (index — this.state.index) }
Now we must have this:
5 — Response when user release the card
The next step is detect when user release the card and check if we make the current card go back to original position or bring the next card on top. To do this we need to add some code on onPanResponderRelease event.
Let’s create 3 new methods to deal with it:
completeSwipe(direction) {
const x = (direction === 'right' ?
SCREEN_WIDTH + 50 : -SCREEN_WIDTH - 50); Animated.timing(this.state.position, {
toValue: { x, y: 0 },
duration: 250
}).start(() => this.onCompleteSwipe());
}onCompleteSwipe() {
this.setState({ index: this.state.index + 1 });
this.state.position.setValue({ x: 0, y: 0 });
}resetPosition() {
this.state.position.setValue({ x: 0, y: 0 });
}
The completeSwipe method will be called when drag and release the card beyond a certain value on X axis. So we create a animation to remove it from screen and call the callback onCompleteSwipe. The onCompleteSwipe method just increment the index and set the default position to next card.
Now we can modify the constructor to use this methods:
//import liraries
import React, { Component } from 'react';
import {
View,
StyleSheet,
Dimensions,
Animated,
PanResponder
}const SCREEN_WIDTH = Dimensions.get('window').width;
const SWIPE_THRESHOLD = SCREEN_WIDTH * 0.40;...constructor(props) {
super(props); const position = new Animated.ValueXY();
const panResponder = PanResponder.create({
onStartShouldSetPanResponder: () => true,
onPanResponderMove: (event, gesture) => {
position.setValue({ x: gesture.dx, y: gesture.dy });
},
onPanResponderRelease: (event, gesture) => {
if (gesture.dx > SWIPE_THRESHOLD)
this.completeSwipe('right');
else if (gesture.dx < -SWIPE_THRESHOLD)
this.completeSwipe('left');
else
this.resetPosition();
}
}); this.state = { panResponder, position, index: 0 };
}...
The SWIPE_THRESHOLD is a constant to calculate how far user can drag the card without let it go and bring another card to top.
Almost done, we must have this:
6 — Final touching
Now let’s make the next card move up smoothly.
Add Animation on resetPosition method:
...resetPosition() {
Animated.spring(this.state.position, {
toValue: { x: 0, y: 0 }
}).start();
}...
Import two components LayoutAnimation and UIManager
//import lirariesimport React, { Component } from 'react';
import {
View,
StyleSheet,
Dimensions,
Animated,
PanResponder,
LayoutAnimation,
UIManager
} from 'react-native';
And use on componentWillUpdate lifecycle method:
... componentWillUpdate() {
UIManager.setLayoutAnimationEnabledExperimental && UIManager.setLayoutAnimationEnabledExperimental(true); LayoutAnimation.spring();
}...
Final version:
Well, I know it was a big post, but you can clone the project on GitHub and use this post to understand what I’ve done in each step. If you have any question let me know!
Thanks for reading!
