FlatList Wave Like Animation

Akbar Julian Khatibi
Cool Things With React Native
4 min readMay 5, 2019

While trying to search on the internet for examples of animations that were similar to this one I could not find any so I decided to write this tutorial to help anyone else who may want to implement something like this or similar.

To describe the animation, it is a FlatList that animates as the component renders by animating each row from the FlatList from the right side of the screen and smoothly slides across to the left side of the screen one by one in a wave-like fashion which then gives an effect like it is pulling towards your palm. The applications of this concept are endless, but here is an example animation above to better portray this idea.

To begin, I will go over some basics such as my development environment. For this example, I am using React Native 0.59.5 and React 16.8.3.

Next, let’s start at the top. Let’s import all necessary items for this example.

import React, { Component } from ‘react’;
import {
StyleSheet,
View,
FlatList,
TouchableOpacity,
Text,
SafeAreaView,
Image,
Animated
} from ‘react-native’;

Aside from the expected React library and FlatList from React Native, we are going to import Animated as well. You will notice that I have also imported SafeAreaView. I have done so just because I am using an iPhone X in this example. It does not affect the animation in any way. It could be just a regular view. Furthermore, let’s continue building this out! Next, the constructor.

constructor(props) {
super(props);
this.delayValue = 500;
this.state = {
animatedValue: new Animated.Value(0),
data:[]
}
}

Now, in our constructor, we initialize the variable delayValue to the class and assign the value 500 as a number. We do this to keep the value consistently starting at 500 when the component mounts. Then we assign properties, animatedValue and data, to the state like so. The data array will be filled with hardcoded values. For purposes of this example I have not filled them out because that should be self-explanatory. For more information about how to use FlatList checkout this link to learn more https://facebook.github.io/react-native/docs/flatlist

Let’s look at our render method to see how this should be used.

render() {
const { data } = this.state;
return (
<SafeAreaView style={styles.container}>
<FlatList
keyExtractor={this._keyExtractor}
data={data}
renderItem={this._renderItem}
/>
</SafeAreaView>
);
}

I’ve deconstructed the state object to get data. I then return FlatList wrapped inside of SafeAreaView. Now for the good stuff! I return a function being passed through renderItem which will be each row we will be iterating along with our data array.

_renderItem = ({ item }) => {
return (
<View
style={styles.button}
>
<View
style={styles.imageContainer}
>
<Image
source={require('./image.png')}
style={styles.image}
/>
</View>
<View
style={styles.insideStyle}
>
<Text style={{ fontWeight: 'bold' }}>{item.name}</Text>
<Text>{item.subtitle}</Text>
</View>
</View>
)
}

Then we style it with this styles object. This also includes the styling for the SafeAreaView wrapped around the FlatList.

const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
button: {
flexDirection: 'row',
alignItems: 'center',
padding: 10,
borderColor: 'black',
borderWidth: 1
},
insideStyle: {
flexDirection: 'column',
alignItems: 'center',
},
imageContainer: {
width: 100,
height: 100,
},
image: {
flex: 1,
width: undefined,
height: undefined,
resizeMode: 'contain',
margin: 20
}
});

Moreover, in our componentDidMount, we animate the value animatedValue using Animated.spring.

componentDidMount = () => {
Animated.spring(this.state.animatedValue, {
toValue: 1,
tension: 20,
useNativeDriver: true
}).start();
}

Further, we replace our regular View component with Animated.View and we create an interpolated value for animatedValue. Since each row will be coming from the left side to the ride side we are going to use this interpolated value to represent our translateX value. When the Animated.spring moves from 0 to 1 it will return the appropriate interpolated value represented in our outputRange. Then we use our delayValue that we initialized earlier and add 500 to each new row in our FlatList to its respective translateX value

_renderItem = ({ item }) => {
this.delayValue = this.delayValue + 500;
const translateX = this.state.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [this.delayValue, 1]
});
return (
<Animated.View
style={[styles.button, { transform: [{ translateX }] }]}
>
<View
style={styles.imageContainer}
>
<Image
source={require('./image.png')}
style={styles.image}
/>
</View>
<View
style={styles.insideStyle}
>
<Text style={{ fontWeight: 'bold' }}>{item.name}</Text>
<Text>{item.subtitle}</Text>
</View>
</Animated.View>
)
}

and Voilà! Now you can study this and use this or use something like this in your next project. Here’s the full code below.

import React, { Component } from ‘react’;
import {
StyleSheet,
View,
FlatList,
TouchableOpacity,
Text,
SafeAreaView,
Image,
Animated
} from ‘react-native’;
export class animationExample extends Component {componentDidMount = () => {
Animated.spring(this.state.animatedValue, {
toValue: 1,
tension: 20,
useNativeDriver: true
}).start();
}
_renderItem = ({ item }) => {
this.delayValue = this.delayValue + 500;
const translateX = this.state.animatedValue.interpolate({
inputRange: [0, 1],
outputRange: [this.delayValue, 1]
});
return (
<Animated.View
style={[styles.button, { transform: [{ translateX }] }]}
>
<View
style={styles.imageContainer}
>
<Image
source={require('./image.png')}
style={styles.image}
/>
</View>
<View
style={styles.insideStyle}
>
<Text style={{ fontWeight: 'bold' }}>{item.name}</Text>
<Text>{item.subtitle}</Text>
</View>
</Animated.View>
)
}
render() {
const { data } = this.state;
return (
<SafeAreaView style={styles.container}>
<FlatList
keyExtractor={this._keyExtractor}
data={data}
renderItem={this._renderItem}
/>
</SafeAreaView>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#F5FCFF',
},
button: {
flexDirection: 'row',
alignItems: 'center',
padding: 10,
borderColor: 'black',
borderWidth: 1
},
insideStyle: {
flexDirection: 'column',
alignItems: 'center',
},
imageContainer: {
width: 100,
height: 100,
},
image: {
flex: 1,
width: undefined,
height: undefined,
resizeMode: 'contain',
margin: 20
}
});

Thanks! I hope this was helpful! Enjoy!

--

--

Akbar Julian Khatibi
Cool Things With React Native

Web/Mobile Dev Expert in React/Native. Building apps & leading teams. Family man & foodie. Sharing dev insights on Medium. khatibi.app