Back to Posts

How to Create a Slide Drawer Menu in Flutter

15 min read

A slide drawer menu is a common UI pattern that provides an elegant way to navigate between different sections of your app. In this guide, we'll explore how to create both basic and custom slide drawer menus in Flutter, complete with smooth animations and gesture controls.

Understanding the Basics

Before diving into custom implementations, let's understand Flutter's built-in Drawer widget and how we can extend it for more advanced functionality.

Basic Drawer Implementation

import 'package:flutter/material.dart';

class BasicDrawerExample extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Drawer Example'),
      ),
      drawer: Drawer(
        child: ListView(
          padding: EdgeInsets.zero,
          children: [
            DrawerHeader(
              decoration: BoxDecoration(
                color: Theme.of(context).primaryColor,
              ),
              child: Text(
                'App Menu',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 24,
                ),
              ),
            ),
            ListTile(
              leading: Icon(Icons.home),
              title: Text('Home'),
              onTap: () {
                Navigator.pop(context);
              },
            ),
            ListTile(
              leading: Icon(Icons.settings),
              title: Text('Settings'),
              onTap: () {
                Navigator.pop(context);
              },
            ),
          ],
        ),
      ),
      body: Center(
        child: Text('Main Content'),
      ),
    );
  }
}

Creating a Custom Slide Drawer

Now, let's create a custom slide drawer with smooth animations and gesture control.

import 'package:flutter/material.dart';

class CustomSlideDrawer extends StatefulWidget {
  final Widget mainContent;
  final Widget drawerContent;

  CustomSlideDrawer({
    required this.mainContent,
    required this.drawerContent,
  });

  @override
  _CustomSlideDrawerState createState() => _CustomSlideDrawerState();
}

class _CustomSlideDrawerState extends State<CustomSlideDrawer>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  bool _isDrawerOpen = false;
  final double _maxSlide = 255.0;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 250),
    );
  }

  void _toggleDrawer() {
    if (_isDrawerOpen) {
      _controller.reverse();
    } else {
      _controller.forward();
    }
    setState(() {
      _isDrawerOpen = !_isDrawerOpen;
    });
  }

  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onHorizontalDragUpdate: (details) {
        double delta = details.delta.dx / _maxSlide;
        _controller.value += delta;
      },
      onHorizontalDragEnd: (details) {
        if (_controller.value < 0.5) {
          _controller.reverse();
          setState(() {
            _isDrawerOpen = false;
          });
        } else {
          _controller.forward();
          setState(() {
            _isDrawerOpen = true;
          });
        }
      },
      child: AnimatedBuilder(
        animation: _controller,
        builder: (context, _) {
          return Stack(
            children: [
              Transform.translate(
                offset: Offset(_maxSlide * _controller.value, 0),
                child: widget.mainContent,
              ),
              Transform.translate(
                offset: Offset(
                  _maxSlide * (_controller.value - 1),
                  0,
                ),
                child: widget.drawerContent,
              ),
            ],
          );
        },
      ),
    );
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

Using the Custom Slide Drawer

Here's how to implement the custom slide drawer in your app:

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: CustomSlideDrawer(
          drawerContent: Container(
            color: Colors.blue[800],
            child: Column(
              children: [
                SizedBox(height: 50),
                ListTile(
                  leading: Icon(Icons.home, color: Colors.white),
                  title: Text(
                    'Home',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
                ListTile(
                  leading: Icon(Icons.person, color: Colors.white),
                  title: Text(
                    'Profile',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
                ListTile(
                  leading: Icon(Icons.settings, color: Colors.white),
                  title: Text(
                    'Settings',
                    style: TextStyle(color: Colors.white),
                  ),
                ),
              ],
            ),
          ),
          mainContent: Scaffold(
            appBar: AppBar(
              leading: IconButton(
                icon: Icon(Icons.menu),
                onPressed: () {
                  // Access the drawer state
                  final state = context.findAncestorStateOfType<_CustomSlideDrawerState>();
                  state?._toggleDrawer();
                },
              ),
              title: Text('Custom Drawer Example'),
            ),
            body: Center(
              child: Text('Main Content Area'),
            ),
          ),
        ),
      ),
    );
  }
}

Advanced Features

Adding Shadow and Scale Animation

Let's enhance our drawer with a shadow effect and scale animation:

class AdvancedSlideDrawer extends StatefulWidget {
  final Widget mainContent;
  final Widget drawerContent;

  AdvancedSlideDrawer({
    required this.mainContent,
    required this.drawerContent,
  });

  @override
  _AdvancedSlideDrawerState createState() => _AdvancedSlideDrawerState();
}

class _AdvancedSlideDrawerState extends State<AdvancedSlideDrawer>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;
  late Animation<double> _scaleAnimation;
  late Animation<double> _shadowAnimation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: Duration(milliseconds: 250),
    );
    
    _scaleAnimation = Tween<double>(
      begin: 1.0,
      end: 0.8,
    ).animate(_controller);
    
    _shadowAnimation = Tween<double>(
      begin: 0.0,
      end: 10.0,
    ).animate(_controller);
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, _) {
        return Stack(
          children: [
            widget.drawerContent,
            Transform(
              transform: Matrix4.identity()
                ..translate(_controller.value * 200.0)
                ..scale(_scaleAnimation.value),
              child: Container(
                decoration: BoxDecoration(
                  boxShadow: [
                    BoxShadow(
                      color: Colors.black.withOpacity(0.3),
                      blurRadius: _shadowAnimation.value,
                    ),
                  ],
                ),
                child: widget.mainContent,
              ),
            ),
          ],
        );
      },
    );
  }
}

Best Practices

  1. Performance

    • Use RepaintBoundary for complex drawer content
    • Optimize animations for smooth performance
    • Cache drawer items when possible
  2. User Experience

    • Provide visual feedback for interactions
    • Implement proper gesture handling
    • Maintain consistent animation timing
  3. Accessibility

    • Support screen readers
    • Implement proper navigation semantics
    • Provide sufficient touch targets
  4. Responsiveness

    • Handle different screen sizes
    • Adjust drawer width based on device
    • Support both portrait and landscape modes

Common Issues and Solutions

1. Gesture Conflict Resolution

Handle gesture conflicts with other widgets:

GestureDetector(
  onHorizontalDragStart: (details) {
    // Check if drag started from edge
    if (details.globalPosition.dx < 20) {
      // Allow drawer gesture
    }
  },
  // Other gesture handlers
)

2. State Management

Manage drawer state effectively:

class DrawerState extends ChangeNotifier {
  bool _isOpen = false;
  
  bool get isOpen => _isOpen;
  
  void toggleDrawer() {
    _isOpen = !_isOpen;
    notifyListeners();
  }
}

Conclusion

Creating a custom slide drawer menu in Flutter allows you to build unique and engaging navigation experiences. By following these implementation patterns and best practices, you can create smooth, responsive drawer menus that enhance your app's usability. Remember to consider performance, accessibility, and user experience when implementing your drawer solution.