DEV Community

Cover image for React Native Travel Article App UI Clone #5 : Animated Delimiter
0xAirdropfarmer
0xAirdropfarmer

Posted on • Originally published at kriss.io on

React Native Travel Article App UI Clone #5 : Animated Delimiter

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.

In case of wanting to learn from the beginning, all the parts for this tutorial series are available below:

- [React Native Travel Article App UI Clone #1: Getting Started](https://kriss.io/react-native-travel-article-app-ui-clone-1-getting-started/)
- [Building React Native Travel Article App UI Clone #2: Destinations Section](https://kriss.io/react-native-travel-article-app-ui-clone-2-destinations-section/)
- [React Native Travel Article App UI Clone #3: Destinations Info Section](https://kriss.io/react-native-travel-article-app-ui-clone-3-destination-info-section/)
- [React Native Travel Article App UI Clone #4: Recommendation Section](https://kriss.io/react-native-travel-article-app-ui-clone-4-recommendation-section/)

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.

Overview

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!!

Making Some Configuration in FlatList of Destinations section

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>

Adding Animation to Delimiter Dots

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 scrolling

Here, 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.

Configuring Style properties

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.

Integrating Color and Size style properties based on theme.js file

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>

Changing size properties in accordance with theme.js

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.

Conclusion

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.

The post React Native Travel Article App UI Clone #5 : Animated Delimiter Dots appeared first on Kriss.

Top comments (0)