DEV Community

Kadi Kraman
Kadi Kraman

Posted on

Building an Animated Drawer in React Native

Animated drawer example

Animations are a sure way to improve user experience and delight in your app. However, they are often not that straightforward to implement, and so end up as a "nice to have" at the very bottom of the backlog.

React Native actually comes with some rather handy features that make adding certain types of animations a lot more straightforward than you might imagine!

In this tutorial, I will show you how to create an animated drawer component in React Native - without using the Animated API!

LayoutAnimation

The key to building this is LayoutAnimation. It is a built in feature that automagically animates views to the new position when a layout change happens.

Let's demonstrate how this works by animating a box to grow and shrink onPress.

Box growing and shrinking on an iPhone

First let's create a box:

import React from "react";
import { View, SafeAreaView, StyleSheet } from "react-native";

const App = () => {
  return (
    <SafeAreaView style={styles.safeArea}>
      <View style={styles.container}>
        <View style={styles.box} />
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  safeArea: {
    flex: 1
  },
  container: {
    justifyContent: "center",
    alignItems: "center",
    flex: 1
  },
  box: {
    backgroundColor: "pink",
    height: 50,
    width: 50
  }
});

export default App;

Now let's update out code so that the box toggles from being 50x50 to 200x200 in size when you tap on it.

  • use the useState hook to hold the state for whether the box is expanded
  • update the box style so that it is 200x200 when isExpanded is true
  • add a Touchable around the box and toggle expanded state onPress
-import React from "react";
+import React, { useState, useCallback } from "react";
-import { View, SafeAreaView, StyleSheet } from "react-native";
+import { View, SafeAreaView, StyleSheet, TouchableOpacity } from "react-native";

const App = () => {
+  const [isExpanded, setIsExpanded] = useState(false);
+  const handlePress = useCallback(() => {
+    setIsExpanded(val => !val);
+  }, []);
  return (
    <SafeAreaView style={styles.safeArea}>
      <View style={styles.container}>
+        <TouchableOpacity onPress={handlePress}>
-          <View style={styles.box} />
+          <View style={[styles.box, isExpanded ? styles.expanded : undefined]} />
+        </TouchableOpacity>
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  safeArea: {
    flex: 1
  },
  container: {
    justifyContent: "center",
    alignItems: "center",
    flex: 1
  },
  box: {
    backgroundColor: "pink",
    height: 50,
    width: 50
-  }
+  },
+  expanded: {
+    height: 200,
+    width: 200
+  }
});

export default App;

Now the box toggles from being 50x50 to 200x200, but there's no animation. This is where LayoutAnimation comes to play. We want to tell the layout that the next time there is a UI change, instead of snapping to it, animate.

import React, { useState, useCallback } from "react";
-import { View, SafeAreaView, StyleSheet, TouchableOpacity } from "react-native";
+import { View, SafeAreaView, StyleSheet, TouchableOpacity, LayoutAnimation } from "react-native";

const App = () => {
  const [isExpanded, setIsExpanded] = useState(false);
  const handlePress = useCallback(() => {
    setIsExpanded(val => !val);
+    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
  }, []);
  return (
    <SafeAreaView style={styles.safeArea}>
      <View style={styles.container}>
        <TouchableOpacity onPress={handlePress}>
          <View style={[styles.box, isExpanded ? styles.expanded : undefined]} />
        </TouchableOpacity>
      </View>
    </SafeAreaView>
  );
};

const styles = StyleSheet.create({
  safeArea: {
    flex: 1
  },
  container: {
    justifyContent: "center",
    alignItems: "center",
    flex: 1
  },
  box: {
    backgroundColor: "pink",
    height: 50,
    width: 50
  },
  expanded: {
    height: 200,
    width: 200
  }
});

export default App;

Note that if you need this to work on Android as well, you'll need to place this extra it of code just under your import statements:

import { UIManager, Platform } from "react-native";

if (Platform.OS === "android") {
  if (UIManager.setLayoutAnimationEnabledExperimental) {
    UIManager.setLayoutAnimationEnabledExperimental(true);
  }
}

That's literally it! Sure, there are limits to what you can achieve with this, but there is quite a bit you can use LayoutAnimation for with very little additional developer effort.

Check out the source code for the animated box here

Building an animated drawer

Now that you have the power of LayoutAnimation in your back pocket, you can use this to build a drawer.

Let's see how to build a single drawer. The core concept is this:

  1. create a touchable area which is the always visible title
  2. add a list of elements underneath it that should be displayed when the drawer is open
  3. wrap the list of element in a view and give it a conditional style that sets the view height to 0 when the list is "closed"
  4. call LayoutAnimation.configureNext immediately after triggering the state change to open or close the drawer.

The code for a single section should look like this:

import React, { useState, useCallback } from "react";
import {
  View,
  Text,
  FlatList,
  LayoutAnimation,
  TouchableOpacity,
  UIManager,
  Platform
} from "react-native";

if (Platform.OS === "android") {
  if (UIManager.setLayoutAnimationEnabledExperimental) {
    UIManager.setLayoutAnimationEnabledExperimental(true);
  }
}

const Section = () => {
  const [isOpen, setIsOpen] = useState(false);

  const toggleOpen = useCallback(() => {
    setIsOpen(value => !value);
    LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
  }, []);

  return (
    <>
      <TouchableOpacity onPress={toggleOpen}>
        <Text>Section header</Text>
      </TouchableOpacity>
      <View style={!isOpen ? { height: 0 } : undefined}>
        <FlatList
          data={["Item 1", "Item 2", "Item 3", "Item 4"]}
          keyExtractor={item => item}
          renderItem={({ item }) => (
            <View style={styles.item}>
              <Text>{item}</Text>
            </View>
          )}
        />
      </View>
    </>
  );
};

export default Section;

Now all that's left to do is add some styling and a couple more sections and you're all done! Check out the source code for the animated drawer here

I would also recommend reading up on LayoutAnimation to learn more about ways you can customise it.

Layout Animation in the Wild

Here is an example of a feature in a production app that I built using LayoutAnimation. The drawer here is built using exactly the method described in this article!

Alt mode select example using LayoutAnimation

Top comments (0)