Search code examples
react-native-navigationreact-native-reanimatedreact-native-reanimated-v2react-native-navigation-v2react-native-drawer

Custom drawer navigation isn't animating, just getting snapped to final size


I'm trying to implement this custom drawer with animation effect while sliding (opening, closing), the MainLayout should be resizing with a shrinking / growing animation when toggeling the drawer but what it does is simply changing it's size after it reache the right or left end, infact if I'm using the close button insted of pull / push through touch, upon returning it isn't growing in full sceen.

Code for Drawer:

import 'react-native-gesture-handler';
import React, {useState} from 'react';
import {StyleSheet, TouchableOpacity, Image, Text, View} from 'react-native';
import LinearGradient from 'react-native-linear-gradient';
import Animated from 'react-native-reanimated';
import {
  createDrawerNavigator,
  DrawerContentScrollView,
  useDrawerProgress,
} from '@react-navigation/drawer';
import {icons} from '../constants';

// screens import
import MainLayout from '../screens/MainLayout';

const Drawer = createDrawerNavigator();

const CustomDrawerItem = ({icon, label}) => {
  return (
    <TouchableOpacity // drawer item button
      style={[styles.drawerItem]}
      onPress={() => console.log('drawer item')}>
      <Image // drawer item icon
        source={icon}
        style={styles.drawerItemIcon}
      />
      <Text // drawer item label
        style={styles.drawerItemLabel}>
        {label}
      </Text>
    </TouchableOpacity>
  );
};

const CustomDrawerContent = ({navigation}) => {
  return (
    <DrawerContentScrollView
      scrollEnabled={true}
      contentContainerStyle={{flex: 1}}>
      <View style={{flex: 1, paddingHorizontal: 20}}>
        <View // close button container
          style={styles.closeBtnContainer}>
          <TouchableOpacity // close button
            style={styles.closeBtn}
            onPress={() => navigation.closeDrawer()}>
            <Image // close button icon
              source={icons.close}
              style={styles.closeBtnIcon}
              resizeMode="contain"
            />
          </TouchableOpacity>
        </View>

        <TouchableOpacity // profile button
          style={styles.profileButton}
          onPress={() => console.log('profile')}>
          <Image // profile image
            source={icons.avatar}
            style={styles.profileImage}
            resizeMode="cover"
          />
          <View // profile text container
            style={{marginLeft: 10}}>
            <Text // profile name
              style={styles.commonTxt}>
              John Doe
            </Text>
            <Text // profile email
              style={[styles.commonTxt, {fontSize: 15}]}>
              [email protected]
            </Text>
          </View>
        </TouchableOpacity>

        <View // drawer items container
          style={styles.drawerItmContainer}>
          <CustomDrawerItem // qr code
            icon={icons.qr_code}
            label="QR Code"
          />

          <CustomDrawerItem // resource
            icon={icons.resource}
            label="Resource"
          />

          <CustomDrawerItem // collection report
            icon={icons.collection_report}
            label="Collection Report"
          />

          <CustomDrawerItem // change pin
            icon={icons.change_pin}
            label="Change Pin"
          />
          <View // separator
            style={styles.separator}
          />
        </View>
        <View // logout button container
          style={{marginBottom: 20}}>
          <CustomDrawerItem // logout
            icon={icons.logout}
            label="Logout"
          />
        </View>
      </View>
    </DrawerContentScrollView>
  );
};

const CustomDrawer = () => {
  return (
    <LinearGradient
      colors={['#ff9933', '#3b5998', '#192f6a']}
      style={styles.container}>
      <Drawer.Navigator
        screenOptions={{
          headerShown: false,
          drawerType: 'slide',
          overlayColor: 'transparent',
          drawerStyle: {flex: 1, width: '65%', backgroundColor: 'transparent'},
          sceneContainerStyle: {backgroundColor: 'transparent'},
        }}
        drawerContent={props => {
          return <CustomDrawerContent navigation={props.navigation} />;
        }}
        initialRouteName="MainLayout">
        <Drawer.Screen name="MainLayout">
          {props => <MainLayout {...props} />}
        </Drawer.Screen>
      </Drawer.Navigator>
    </LinearGradient>
  );
};

export default CustomDrawer;

const styles = StyleSheet.create({
  drawerItem: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 20,
    alignItems: 'center',
    borderRadius: 10,
  },
  drawerItemIcon: {width: 20, height: 20, tintColor: '#fff'},
  drawerItemLabel: {fontSize: 18, color: '#fff', marginLeft: 15},
  profileButton: {flexDirection: 'row', alignItems: 'center', marginTop: 20},
  profileImage: {width: 50, height: 50, borderRadius: 10},
  closeBtnContainer: {alignItems: 'flex-start', justifyContent: 'center'},
  closeBtn: {alignItems: 'center', justifyContent: 'center'},
  closeBtnIcon: {width: 30, height: 30, tintColor: '#fff'},
  commonTxt: {fontSize: 18, color: '#fff'},
  drawerItmContainer: {flex: 1, marginTop: 20},
  separator: {height: 1, backgroundColor: '#fff', marginVertical: 10},
  container: {flex: 1, backgroundColor: '#ff9f1c'},
});


Code for MainLayout:

import 'react-native-gesture-handler';
import {StyleSheet, Text, View, SafeAreaView} from 'react-native';
import React from 'react';
import Animated from 'react-native-reanimated';
import {useDrawerProgress} from '@react-navigation/drawer';

const MainLayout = props => {
  const progress = useDrawerProgress();

  const scale = Animated.interpolateNode(progress.value, {
    inputRange: [0, 1],
    outputRange: [1, 0.75],
  });
  const borderRadius = Animated.interpolateNode(progress.value, {
    inputRange: [0, 1],
    outputRange: [0, 30],
  });

  const animatedStyle = {
    borderRadius,
    transform: [{scale}],
  };

  return (
    <Animated.View
      style={{
        flex: 1,
        alignItems: 'center',
        justifyContent: 'center',
        backgroundColor: '#fff',
        ...animatedStyle,
      }}>
      <Text>MainLayout</Text>
    </Animated.View>
  );
};

export default MainLayout;

const styles = StyleSheet.create({});

Animation Issue:

Animation Issue

Closing Issue:

Closing Issue

package.json

{
  "name": "Dhananjaya",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "android": "react-native run-android",
    "ios": "react-native run-ios",
    "start": "react-native start",
    "test": "jest",
    "lint": "eslint ."
  },
  "dependencies": {
    "@react-native-masked-view/masked-view": "^0.2.6",
    "@react-navigation/drawer": "^6.4.1",
    "@react-navigation/native": "^6.0.10",
    "@react-navigation/stack": "^6.2.1",
    "axios": "^0.27.2",
    "native-base": "^3.4.5",
    "react": "17.0.2",
    "react-native": "0.68.2",
    "react-native-gesture-handler": "^2.4.2",
    "react-native-linear-gradient": "^2.5.6",
    "react-native-reanimated": "^2.8.0",
    "react-native-safe-area-context": "^4.2.5",
    "react-native-screens": "^3.13.1",
    "react-native-svg": "^12.3.0"
  },
  "devDependencies": {
    "@babel/core": "7.18.2",
    "@babel/runtime": "7.18.3",
    "@react-native-community/eslint-config": "2.0.0",
    "babel-jest": "26.6.3",
    "eslint": "7.32.0",
    "jest": "26.6.3",
    "metro-react-native-babel-preset": "0.67.0",
    "react-test-renderer": "17.0.2"
  },
  "jest": {
    "preset": "react-native"
  }
}

babel.config.js (for reference to reanimated configuration)

module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: ['react-native-reanimated/plugin'],
};


Solution

  • Looks like I fixed the problem, I just had to add "useLegacyImplementation" inside screenOptions prop of Drawer, Thats it.

     <Drawer.Navigator
        detachInactiveScreens={true}
        // backBehavior="history"
        screenOptions={{
          headerShown: false,
          drawerType: 'slide',
          overlayColor: 'transparent',
          drawerStyle: {flex: 1, width: '65%', backgroundColor: 'transparent'},
          sceneContainerStyle: {backgroundColor: 'transparent'},
        }}
        drawerContent={props => {
          return <CustomDrawerContent navigation={props.navigation} />;
        }}
        useLegacyImplementation // <-- THIS LINE
        initialRouteName="Tabs">
        <Drawer.Screen
          name="Tabs"
          component={Tabs}
          options={{unmountOnBlur: true}}
        />
        <Drawer.Screen
          name="Profile"
          component={Profile}
          options={{unmountOnBlur: true}}
        />
        <Drawer.Screen
          name="Map"
          component={Map}
          options={{unmountOnBlur: true}}
        />
      </Drawer.Navigator>
    

    enter image description here