This tutorial is the fifth part of our React Native Travel Article App UI clone series. In the previous part, we successfully implemented the Recommendation Section on our List Screen. This tutorial is the continuation of the same tutorial from where we left off in the last part. So, it is recommended to go through the previous parts in order to get the knowledge and insight into the overall project.
The motivation for this tutorial series came from the React Native App Templates that consists of a wide variety of beautiful templates written in React Native and powered by universal features and design. The templates enable us to implement our own apps and even help us get started with our own startups. And, this fifth part is also the continuation of the same coding implementations and designs from the Youtube video tutorial by React UI Kit for the Travel Article App UI clone. The video tutorial delivers the overall tutorial using fast coding which may be difficult to grasp for any developer especially the beginners. Hence, this article tutorial provides a step by step implementation which will be easier to understand and implement. So, the learners can relax and take their time to implement the UI.
In this fifth part of this tutorial series, we are going to implement the Animated Delimeter dots of the Destinations section. The idea is to make the delimiter dots active in the animated fashion while scrolling the Destination section cards. After we complete this, we are going to store our color and size style properties in a different file. Then, we are going to use the style properties from that file to style the components in our List Screen. This will make our code clean and organized for better understanding.
So, let us begin!!
Here, we are going to add an additional prop to the FlatList component of the Destinations section. The prop we are going to add is decelerationRate. This decelerationRate prop is a floating-point number that determines how quickly the scroll view decelerates after the user lifts their finger. Now, we are going to add this prop to our FlatList component as shown in the code snippet below:
<View style={[ styles.flex, styles.column, styles.destinations]}>
<FlatList
horizontal
pagingEnabled
scrollEnabled
showsHorizontalScrollIndicator = {false}
scrollEventThrottle = {16}
snapToAlignment = "center"
decelerationRate={0}
// style={{ overflow : 'visible' }} //In IOS platform
data = {destinations}
keyExtractor = {(item, index)=> `${item.id}`}
renderItem = {({item}) => this.renderDestination(item)}
/>
{this.renderDots()}
</View>
In this step, we are going to add the Animation to our Delimiter Dots in our Destinations section. For that, we need to import the Animated component from the react-native package as shown in the code snippet below:
import {
StyleSheet,
Platform,
Text,
View,
ScrollView,
Dimensions,
ImageBackground,
Image,
FlatList,
TouchableOpacity,
Animated
} from 'react-native';
Now, we need to define a variable called scrollX that is initialized to Animated value. This variable will store our animation value for horizontal animation. In order to do this, we need to use the code from the following code snippet:
export default class List extends React.Component {
scrollX = new Animated.Value(0);
Here, the Animated.Value configuration enables us to bind to style properties or other props and can be interpolated as well. Now, we need to configure this
scrollX
value into the onScroll event of the FlatList
component of renderDestinations() function as shown in the code snippet below: <FlatList
horizontal
pagingEnabled
scrollEnabled
showsHorizontalScrollIndicator = {false}
scrollEventThrottle = {16}
snapToAlignment = "center"
decelerationRate={0}
// style={{ overflow : 'visible' }} //In IOS platform
onScroll={Animated.event([{ nativeEvent: { contentOffset: { x: this.scrollX }} }])}
data = {destinations}
keyExtractor = {(item, index)=> `${item.id}`}
renderItem = {({item}) => this.renderDestination(item)}
/>
Here, we have used the event function of the Animated component which takes the nativeEvent as a parameter. Then, we have defined the
contentOffset
value according to the scrollX
variable inside the nativeEvent config. Configuring Animation in Delimiter dots in accordance with the
FlatList
scrollingHere, we are going to add animation to the Delimiter dots which will depend on the onScroll event of the
FlatList
. For that, we need to first initialize the dot position using the divide function of the Animated
component. The divide function creates a new Animated value composed by dividing the first Animated value by the second Animated value. And, its use is shown in the code snippet below: const dotPosition = Animated.divide(this.scrollX, width);
Now, we are going to make some configuration in the renderDots() function in order to animation properties to the
Delimiter
dots. For that, we need to use the code from the code snippet below:renderDots() {
const dotPosition = Animated.divide(this.scrollX, width);
return (
<View style={[
styles.flex, styles.row,
{ justifyContent: 'center', alignItems: 'center', marginTop: 10 }
]}>
{destinations.map((item, index) => {
const borderWidth = dotPosition.interpolate({
inputRange: [index -1, index, index + 1],
outputRange: [0, 2.5, 0],
extrapolate: 'clamp'
});
return (
<Animated.View
key={`step-${item.id}`}
style={[styles.dots, styles.activeDot, { borderWidth: borderWidth } ]}
/>
)
})}
</View>
)
}
Here, we have defined a constant called
borderWidth
which is initialized to the interpolate() function of the dotPosition
constant initlized to Animated.divide. The interpolate() function takes inputRange
, outputRange
and extrapolate as parameter values. The interpolate() function permits input ranges to map to different output ranges. Then, we have also added
Animated
component to our View
component with borderWidth
style property. Hence, we will get the following result in our emulator screen:
As we can see, we have successfully added beautiful animation to the Delimiter dots as we scroll the Destination section card in the Destination section. The animation is very smooth and works well with the scrolling transition.
Here, we are going to configure the style properties from a separate file. Then, we will style our List Screen components according to the style properties in that file.
Storing Style Properties in Different File
In this step, we are going to store our size and color properties used very commonly on the List screen to a different file called
‘theme.js’
. This will make things easier for us to assign styles to different components. Now, we need to create a file called ‘theme.js’
in our main project folder as shown in the code snippet below:As we can see, we have got the
‘theme.js’
file. Now, in the theme.js file we need to define our colors and sizes properties as shown in the code snippet below: const colors = {
black: '#000',
white: '#FFF',
gray: '#DCE0E9',
caption: '#BCCCD4',
active: '#007BFA',
};
const sizes = {
base: 16,
font: 14,
padding: 36,
margin: 36,
title: 24,
radius: 12,
};
export {
colors,
sizes,
};
Here, we have defined the color and size style properties in the colors and sizes constant variables that are then exported.
Since we have defined some common style properties in the theme.js file, here we are going to import the styles from
theme.js
file and integrate them into our component inline styles as well as the styles in the StyleSheet
component. But first, we need to import the theme.js
file into our List.js
file as shown in the code snippet below:import * as theme from '../theme';
Now, we are going to change the color and size style properties in accordance with the pre-defined styles from the theme.js file.
Changing color style properties in accordance with theme.js
Here, we are going to change the color style properties in the
List.js
file with the colors variable from the theme.js file. All the changes made to the style properties in the StyleSheet
component is provided in the code snippet below: header: {
backgroundColor: theme.colors.white,
paddingHorizontal: 36,
paddingTop: 48,
paddingBottom: 24,
justifyContent: 'space-between',
alignItems: 'center',
},
destinationInfo : {
position : 'absolute',
borderRadius : 12,
paddingHorizontal : 36,
paddingVertical : 16,
bottom : 20, //In iOS platform, bottom : -36
left : 36,
right : 36,
backgroundColor : theme.colors.white,
},
recommendation: {
width: (width - (36 * 2)) / 2,
marginHorizontal: 8,
backgroundColor: theme.colors.white,
overflow: 'hidden',
borderRadius: 12,
marginVertical: 36 * 0.5,
},
recommendationTemp: {
fontSize: 14 * 1.25,
color: theme.colors.white
},
rating: {
fontSize: 28,
color: theme.colors.white,
fontWeight: 'bold'
},
shadow : {
shadowColor: theme.colors.black,
shadowOffset: {
width: 0,
height: 6,
},
shadowOpacity: 0.05,
shadowRadius: 10,
elevation: 5,
},
dots: {
width: 10,
height: 10,
borderWidth: 2.5,
borderRadius: 5,
marginHorizontal: 6,
backgroundColor: theme.colors.gray,
borderColor: 'transparent',
},
activeDot: {
width: 12.5,
height: 12.5,
borderRadius: 6.25,
borderColor: theme.colors.active,
}
Now, we have some inline styles as well bound to the components in the different templating methods. We need to change them in accordance with colors variable as well. All overall code with changes is provided in the code snippets below:
In the header config of the
navigationOptions
object: <Text style={{color : theme.colors.caption}}>Search for place</Text>
For the renderDestination() function:
renderDestination(item){
return(
<ImageBackground
style={[styles.flex, styles.destination, styles.shadow]}
imageStyle = {{borderRadius : 12}}
source= {{uri : item.preview}}
>
<View style={[styles.row, {justifyContent: 'space-between'}]}>
<View style={{flex : 0}}>
<Image source={{uri: item.user.avatar}} style={styles.avatar}/>
</View>
<View style={[styles.column, {flex : 2, paddingHorizontal : 18}]}>
<Text style={{color : theme.colors.white, fontWeight : 'bold'}}>{item.user.name}</Text>
<Text style={{color : theme.colors.white}}>{item.location}</Text>
</View>
<View style={{flex : 0, justifyContent : 'center', alignItems : 'flex-end'}}>
<Text style={styles.rating}>{item.rating}</Text>
</View>
</View>
<View style={[styles.column, styles.destinationInfo, styles.shadow]}>
<Text style={{ fontSize: 24, fontWeight: '500', paddingBottom: 8, }}>
{item.title}
</Text>
<View style={[ styles.row, { justifyContent: 'space-between', alignItems: 'flex-end'}]}>
<Text style={{ color : theme.colors.caption}}>
{item.description.split('').slice(0, 50)}...
</Text>
</View>
</View>
</ImageBackground>
)
}
In the renderRecommended() function
<TouchableOpacity activeOpacity={0.5}>
<Text style={{ color: theme.colors.caption }}>More</Text>
</TouchableOpacity>
For the renderRecommendations() function:
<View style={[styles.flex, styles.column, styles.shadow, { justifyContent: 'space-evenly', padding: 36 / 2 }]}>
<Text style={{ fontSize: 16 * 1.25, fontWeight: '500', paddingBottom: 36 / 4.5, }}>{item.title}</Text>
<Text style={{ color: theme.colors.caption}}>{item.location}</Text>
<View style={[
styles.row,
{ alignItems: 'center', justifyContent: 'space-between', marginTop: 36 }
]}>
<Text style={{ color: theme.colors.active }}>{item.rating}</Text>
</View>
</View>
Here, we are going to change the size style properties in the
List.js
file with the sizes variable from the theme.js file. All the changes made to the style properties in the StyleSheet
component is provided in the code snippet below:header: {
backgroundColor: theme.colors.white,
paddingHorizontal: theme.sizes.padding,
paddingTop: theme.sizes.padding * 1.33,
paddingBottom: theme.sizes.padding * 0.66,
justifyContent: 'space-between',
alignItems: 'center',
},
destinations : {
flex: 2,
justifyContent: 'space-between',
paddingBottom: 30,
},
destination : {
width : width - (theme.sizes.padding * 2),
height : width * 0.66,
marginHorizontal : theme.sizes.margin,
paddingHorizontal : theme.sizes.padding,
paddingVertical : 20,
borderRadius : theme.sizes.radius
},
destinationInfo : {
position : 'absolute',
borderRadius : theme.sizes.radius,
paddingHorizontal : theme.sizes.padding,
paddingVertical : 16,
bottom : 20, //In iOS platform, bottom : -36
left : theme.sizes.padding,
right : theme.sizes.padding,
backgroundColor : theme.colors.white,
},
recommendedHeader: {
justifyContent: 'space-between',
alignItems: 'flex-end',
paddingHorizontal: theme.sizes.padding,
},
recommendation: {
width: (width - (theme.sizes.padding * 2)) / 2,
marginHorizontal: 8,
backgroundColor: theme.colors.white,
overflow: 'hidden',
borderRadius: theme.sizes.radius,
marginVertical: theme.sizes.margin * 0.5,
},
recommendationHeader: {
overflow: 'hidden',
borderTopRightRadius: theme.sizes.radius,
borderTopLeftRadius: theme.sizes.radius,
},
recommendationOptions: {
alignItems: 'center',
justifyContent: 'space-between',
padding: theme.sizes.padding / 2,
position: 'absolute',
top: 0,
left: 0,
right: 0,
},
recommendationTemp: {
fontSize: 14 * 1.25,
color: theme.colors.white
},
recommendationImage: {
width: (width - (theme.sizes.padding* 2)) / 2,
height: (width - (theme.sizes.padding * 2)) / 2,
},
avatar :{
width: theme.sizes.padding,
height : theme.sizes.padding,
borderRadius : theme.sizes.padding / 2
},
rating: {
fontSize: theme.sizes.font * 2,
color: theme.colors.white,
fontWeight: 'bold'
},
Now, we have some inline styles as well bound to the components in the different templating methods. We need to change them in accordance with sizes variable as well. All overall code with changes is provided in the code snippets below:
In the header config of the
navigationOptions
object:<Text style={{fontSize : theme.sizes.font * 2}}>Destination</Text>
For the renderDots() function:
<View
style={[
styles.flex,
styles.row,
{justifyContent: 'center',alignItems:'center', marginTop : Platform.OS === 'ios' ? theme.sizes.padding * 2 : 48}
]}
>
.................
In the renderDestinations() function:
<ImageBackground
style={[styles.flex, styles.destination, styles.shadow]}
imageStyle = {{borderRadius : theme.sizes.radius}}
source= {{uri : item.preview}}
>
<View style={[styles.row, {justifyContent: 'space-between'}]}>
<View style={{flex : 0}}>
<Image source={{uri: item.user.avatar}} style={styles.avatar}/>
</View>
<View style={[styles.column, {flex : 2, paddingHorizontal : theme.sizes.padding / 2}]}>
<Text style={{color : theme.colors.white, fontWeight : 'bold'}}>{item.user.name}</Text>
<Text style={{color : theme.colors.white}}>{item.location}</Text>
</View>
<View style={{flex : 0, justifyContent : 'center', alignItems : 'flex-end'}}>
<Text style={styles.rating}>{item.rating}</Text>
</View>
</View>
<View style={[styles.column, styles.destinationInfo, styles.shadow]}>
<Text style={{ fontSize: theme.sizes.title, fontWeight: '500', paddingBottom: 8, }}>
{item.title}
</Text>
<View style={[ styles.row, { justifyContent: 'space-between', alignItems: 'flex-end'}]}>
<Text style={{ color : theme.colors.caption}}>
{item.description.split('').slice(0, 50)}...
</Text>
</View>
</View>
</ImageBackground>
For renderRecommended() function:
<Text style={{ fontSize: theme.sizes.font * 1.4 }}>Recommended</Text>
In renderRecommendation() function
<View
style={[styles.flex, styles.column, styles.recommendation, styles.shadow,
index === 0 ? { marginLeft: theme.sizes.margin } : null,
isLastItem ? { marginRight: theme.sizes.margin / 2 } : null,
]}
>
<View style={[styles.flex, styles.recommendationHeader]}>
<Image style={[styles.recommendationImage]} source={{ uri: item.preview }} />
<View style={[ styles.flex, styles.row, styles.recommendationOptions ]}>
<Text style={styles.recommendationTemp}>
{item.temperature}℃
</Text>
</View>
</View>
<View style={[styles.flex, styles.column, styles.shadow, { justifyContent: 'space-evenly', padding: theme.sizes.padding / 2 }]}>
<Text style={{ fontSize: theme.sizes.font * 1.25, fontWeight: '500', paddingBottom: theme.sizes.padding / 4.5, }}>{item.title}</Text>
<Text style={{ color: theme.colors.caption}}>{item.location}</Text>
<View style={[
styles.row,
{ alignItems: 'center', justifyContent: 'space-between', marginTop: theme.sizes.margin }
]}>
<Text style={{ color: theme.colors.active }}>{item.rating}</Text>
</View>
</View>
</View>
Hence, after everything is organized, we will get the same result as before which is shown in the following emulator simulation:
As we can see, the result is the same as before with all the codes organized. Now, we will be able to configure style properties easily in the upcoming parts of this tutorial series. With this, we have come to the end of this part of the tutorial series.
Finally, we have successfully implemented the Animated Delimiter dots in the
Destinations
section of our List
screen.This tutorial is the fifth part of the React Native Travel Article App UI clone tutorial series. In this part, we continued from where we left off in the fourth part of this tutorial series. In this part of the tutorial, we got stepwise guidance on how to add beautiful active style animation to the
Delimiter dots
of the Destinations
section. Hence, we made the active animation of Delimeter dots
in accordance with the scrolling of the Destination
section cards. Then, we also organized our inline styles and styles in the StyleSheet
component to make code standard and clear. Hence, we can now style our components more easily in the upcoming parts of this tutorial series.In the next part of this tutorial series, we are going to add the icons to our List screen as well as the rating stars to our Recommendation Info cards.