Search code examples
animationreact-nativereact-native-gesture-handler

BottomSheet animation in react-native doesnt work properly for the first time


I'm trying to create this animation for my bottomsheet ,so initially when my homescreen loads, the bottomsheet pops up from below with a spring animation and then when the user swipes up , the bottomsheet also swipes up witha spring animation to it, and again if the user swipes down the bottomsheet comes back to its original position with a spring animation to it.

The only problem i'm facing is that, the first swipe up that i make after the initial popping up spring animation happens, the animation for the swiping up, doesnt happen smoothly, and it snaps to the upper position in just an instant without showing any spring like animation

Here is my code for my BottomSheet.

import * as React from "react";
import { SCREEN_HEIGHT } from "../../assets/constants";
import { StyleSheet, Animated, TextInput, View } from "react-native";
import { PanGestureHandler } from "react-native-gesture-handler";
import { Easing } from "react-native-reanimated";
import BottomSheetStack from "./BottomSheetStack";

export default class BottomSheet extends React.Component {
  constructor(props) {
    super(props);
    this.BottomSheet = new Animated.Value(0);
    this.state = {
      isOpen: false,
      isLoading: false,
      isPanChanged: false,
    };
  }

  componentDidMount() {
    setTimeout(() => this.animateTranslate(), 1000);
  }

  animateTranslate() {
    Animated.spring(this.BottomSheet, {
      toValue: 1,
      duration: 500,
      useNativeDriver: true,
      easing: Easing.ease,
    }).start();
  }

  translateDrawer() {
    return this.BottomSheet.interpolate({
      inputRange: [0, 1],
      outputRange: [SCREEN_HEIGHT, SCREEN_HEIGHT / 1.48],
    });
  }

  animateBottomSheetUp() {
    this.BottomSheet.setValue(0);
    Animated.spring(this.BottomSheet, {
      toValue: 1,
      duration: 100,
      useNativeDriver: true,
      easing: Easing.ease,
    }).start();
  }
  animateBottomSheetDown() {
    this.BottomSheet.setValue(0);
    Animated.spring(this.BottomSheet, {
      toValue: 1,
      duration: 100,
      useNativeDriver: true,
      easing: Easing.ease,
    }).start();
  }

  handlePanGesture(props) {
    this.setState({ isLoading: true });
    if (!this.state.isPanChanged) {
      this.setState({ isPanChanged: true });
    }
    if (!this.state.isOpen && props.nativeEvent.translationY > 0) {
      this.animateBottomSheetUp();
      this.setState({ isOpen: true });
    } else if (this.state.isOpen && props.nativeEvent.translationY < 0) {
      this.animateBottomSheetDown();
      this.setState({ isOpen: false });
    }
  }

  openBottomSheetHandler() {
    return this.BottomSheet.interpolate({
      inputRange: [0, 1],
      outputRange: [SCREEN_HEIGHT / 1.48, SCREEN_HEIGHT / 2.1],
    });
  }
  closeBottomSheetHandler() {
    return this.BottomSheet.interpolate({
      inputRange: [0, 1],
      outputRange: [SCREEN_HEIGHT / 2.1, SCREEN_HEIGHT / 1.48],
    });
  }

  render() {
    return (
      <PanGestureHandler
        onGestureEvent={(props) => this.handlePanGesture(props)}
      >
        <Animated.View
          style={{
            ...StyleSheet.absoluteFill,
            height: SCREEN_HEIGHT - SCREEN_HEIGHT / 2.2,
            backgroundColor: "#ffffff",
            elevation: 10,
            transform: [
              {
                translateY: !this.state.isLoading
                  ? this.translateDrawer()
                  : !this.state.isOpen && this.state.isPanChanged
                  ? this.openBottomSheetHandler()
                  : this.closeBottomSheetHandler(),
              },
            ],
            alignItems: "center",
            borderRadius: 15,
          }}
        >
          <View style={styles.bottomSheetLine} />
          <TextInput
            value={this.state.name}
            style={styles.searchBox}
            placeholder="Search"
            placeholderTextColor="#5DB075"
          />
          <BottomSheetStack {...this.props.navigation} />
        </Animated.View>
      </PanGestureHandler>
    );
  }
}

const styles = StyleSheet.create({
  searchBox: {
    height: 45,
    width: `85%`,
    backgroundColor: "#ffffff",
    borderRadius: 10,
    color: "#5DB075",
    paddingLeft: 20,
    fontSize: 14,
    letterSpacing: 0.5,
    top: 20,
    fontFamily: "regular",
    borderColor: "#5DB075",
    borderWidth: 1,
  },
  bottomSheetLine: {
    height: 5,
    backgroundColor: "#5DB075",
    width: 70,
    borderRadius: 5,
    top: 5,
  },
});

Please suggest me some idea on how i can achieve this, or any other method that can be used to achieve this.


Solution

  • So, apparently, somewhere i found that spring animation, toValue:1 doesnt actually go upto 1, and it slightly crosses 1. So that's why my animation was behaving like that i guess.

    The solution to this was, i simply changed my toValue for animateBottomSheetUp and animateBottomSheetDown , went to higher value, i.e toValue:2 for animateBottomSheetUp .

    My changes in my code are very minor , it looks like this ,

    also i removed all the this.BottomSheet.setValue(/*somevalue*/) from my code.

    for animateBottomSheetUp:

      animateBottomSheetUp() {
        Animated.spring(this.BottomSheet, {
          toValue: 2,   // made this change from 1 to 2
          duration: 100,
          useNativeDriver: true,
          easing: Easing.ease,
        }).start();
      }
    

    and the openBottomSheetHandler & closeBottomSheetHandler looks like this now,

      openBottomSheetHandler() {
        return this.BottomSheet.interpolate({
          inputRange: [1, 2],
          outputRange: [SCREEN_HEIGHT / 1.48, SCREEN_HEIGHT / 2.1],
        });
      }
      closeBottomSheetHandler() {
        return this.BottomSheet.interpolate({
          inputRange: [1, 2],
          outputRange: [SCREEN_HEIGHT / 1.48, SCREEN_HEIGHT / 2.1],
        });
      }
    

    I'm posting this answer if anybody faces the same problem,
    Though i have worked around this problem, but i still feel there must be a better way to do this,if anybody has any better way , please answer. :-)