In today's tutorial I will show you how to create an animated view that will appear and disappear and that can be dismissed by swiping down.

What will our application do?

  1. When we press on a button, there is a View that appears from the bottom of the screen;
  2. When we swipe this View down, it will smoothly disappear.

Creating our views

import React, {Fragment, Component} from 'react';
import {StyleSheet, TouchableOpacity, View, Text} from 'react-native';

class App extends Component {
  render() {
    return (
      <Fragment>
        <View style={styles.mainView}>
          <TouchableOpacity style={styles.openButton}>
            <Text style={styles.openButtonText}>Open details</Text>
          </TouchableOpacity>
        </View>
        <View style={styles.subView}>
          <TouchableOpacity
            style={styles.closeButtonContainer}
            onPress={() => {}}>
            <View style={styles.closeButton} />
          </TouchableOpacity>
          <View style={styles.detailsContainer}>
            <Text style={styles.detailsText}>Some random product details</Text>
          </View>
        </View>
      </Fragment>
    );
  }
}

Now, let's style them.

const styles = StyleSheet.create({
  mainView: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#B5F6F8',
  },
  openButton: {
    justifyContent: 'center',
    alignItems: 'center',
    width: 100,
    height: 40,
    backgroundColor: '#5B87E5',
    borderRadius: 30,
  },
  openButtonText: {
    fontSize: 12,
    color: 'white',
  },
  subView: {
    position: 'absolute',
    bottom: 0,
    left: 0,
    right: 0,
    backgroundColor: 'white',
    borderTopLeftRadius: 32,
    borderTopRightRadius: 32,
    height: 200,
  },
  closeButtonContainer: {
    flexDirection: 'column',
    justifyContent: 'center',
    alignItems: 'center',
    paddingTop: 18,
    paddingBottom: 12,
  },
  closeButton: {
    height: 7,
    width: 62,
    backgroundColor: '#D8D8D8',
    borderRadius: 3.5,
  },
  detailsContainer: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },

  detailsText: {
    fontSize: 12,
    fontWeight: '600',
    color: '#4A4A4A',
  },
});

You should have something that looks like this.

Animate the appearance of the subView

When I click on "Open details", the subView should appear from the bottom of the screen. That means that we should change its position. We can do it with the transform property of styling. We need to move our view only vertically ( from the bottom of the screen to the current position), so we will be using the translateY function. To actually animate the appearance of our subView we will use the Animated library which is part of React Native.

import { StyleSheet, TouchableOpacity, View, Text, Animated } from 'react-native'; //importing Animated from 'react-native'


class App extends Component {
  state = {
    translateValue: new Animated.Value(200),// initial value of 'transform: translateY'. 
//As our subView has a height of 200 px to hide it we need to take it down to 200 px.
//To do this we will use 'transform: translateY(200)'
  };

  toggleDetails = shouldOpen => {
    let toValue = 0;// if we need to open our subView, we need to animate it to it original hight.
//To do this, we will use 'transform: translateY(0)' 
    if (!shouldOpen) {
      toValue = 200;
    } // if it's already open and we need to hide it, we will use 'transform: translateY(200)'
    Animated.spring(this.state.translateValue, {
      toValue: toValue,
      velocity: 3,
      tension: 2,
      friction: 8,
    }).start(); // the actual animation
  };

  render() {
    return (
      <Fragment>
        <View style={styles.mainView}>
          <TouchableOpacity
            onPress={() => {
              this.toggleDetails(true);
            }}
            style={styles.openButton}> // when clicking on this button we want to open our subView. 
                //As parameter 'shouldOpen' we pass 'true'. Yes, you should open. 
            <Text style={styles.openButtonText}>Open details</Text>
          </TouchableOpacity>
        </View>
        <Animated.View
          style={[
            styles.subView,
            {transform: [{translateY: this.state.translateValue}]},
          ]}> //that is the View that we want to animate by changing its transform style.
          <TouchableOpacity
            style={styles.closeButtonContainer}
            onPress={() => {
              this.toggleDetails(false);
            }}> // by clicking on this little rectangle we want to hide our subView. 
                // We are now passing 'false' to our function.
            <View style={styles.closeButton} />
          </TouchableOpacity>
          <View style={styles.detailsContainer}>
            <Text style={styles.detailsText}>Some random product details</Text>
          </View>
        </Animated.View>
      </Fragment>
    );
  }
}

If we look at our application now, we will have something like this.

Swipe down to dismiss

To do this, we will be using PanResponder. PanResponder listens to the gesture that user does.

import {
  StyleSheet,
  TouchableOpacity,
  View,
  Text,
  Animated,
  PanResponder,
} from 'react-native'; //importing PanResponder

class App extends Component {
  state = {
    translateValue: new Animated.Value(200),
  };

  constructor(props) {
    super(props);
    this.panResponder = PanResponder.create({
      onStartShouldSetPanResponder: () => true,
      onPanResponderMove: (event, gestureState) => {
        this.state.translateValue.setValue(Math.max(0, 0 + gestureState.dy)); //step 1
      },
      onPanResponderRelease: (e, gesture) => {
        const shouldOpen = gesture.vy <= 0;
        Animated.spring(this.state.translateValue, {
          toValue: shouldOpen ? 0 : 200,
          velocity: gesture.vy,
          tension: 2,
          friction: 8,
        }).start(); //step 2
      },
    });
  }

Step 1: onPanResponderMove catches the movement of your finger while you're dragging the view. Each time your finger moves, this function will get called. This is where we want to update the position of the view, so it follows the finger.

  1. We can move our subview by changing its translateY value. As you can see, this.state.translateValue controls it.
  2. As translateValue is an Animated.Value, we can modify it by calling setValue. You can retrieve the number of pixels that the user moved in the Y direction with "gestureState.dy". We are using Math.max(0, 0 + gestureState.dy) to ensure that user won't be able to move the subView higher than its initial position which is 0.
We don't want this...

Step 2: onPanResponderRelease tells us that the user stopped dragging. Let's review what we did line by line.

  1. gesture.vy (the velocity on the y axis) helps us understand if the user was scrolling up or down. If it's less than 0, it's a swipe up and if it's more than 0, it's a swipe down.
  2. Next, we are animating our element. If it was swiped up (gesture.vy < 0, shouldOpen = true), we animate the subView to value 0 to reset its position. If it was swiped down (gesture.vy > 0, shouldOpen = false), it means that we need to hide our subView by passing its height in the style transform: translateY(), in our case: 200.

Now we need to add our PanResponder to the subView.

<Animated.View
          {...this.panResponder.panHandlers}
          style={[
            styles.subView,
            {transform: [{translateY: this.state.translateValue}]},
          ]}>
              //code
</Animated.View>

If we refresh our application, we should have this results.