Flutter: Sliding Menu Demo

Flutter is getting a lot of hype in the frontend world, and if you have worked with it–you know why.

There are 5 things you need to use to create a sliding menu:

Stateless Widget:
As the name suggests, this is a widget without any state. This type of widget is useful when the data we want to show in the widget is not going to change (e.g Appbar, or something similar).

Stateful Widget:
As the name suggests, it’s a widget with a state. This type of widget is useful in cases where the data we want to show in the widget might change with user interaction (API requests, etc). Whenever there is any change in state of the widget, Flutter will redraw your widget.

Animation Controller:
This is the controller class for an animation. It allows us to play an animation forward, back, or to stop the animation. By default, animation controller linearly produces values that range from 0.0 to 1.0, during a given duration. Animation Controller needs a Ticker Provider.

Ticker Provider:
Ticker provider is an interface that describes a factory for a Ticker object. “Ticker” is an object that knows how to register itself with the “SchedulerBinding” and fires a callback every frame. As a result, any animation controller will not perform any calculation between two frames.

Change Notifier:
This is a class that is used for change notifications. It is observed in Java.

Let’s get started

Step 1.
Create A new Flutter project.
Step 2.
Go to projectName/lib/main.dart and delete everything.
Step 3.
Add below code to main.dart
import 'package:episodie_flutter/SlidingMenuDemo.dart';
import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
 @override
 Widget build(BuildContext context) {
   return new MaterialApp(
     title: "Hidden Drawer",
     home: SlidingMenuDemo(),
   );
 }
}

NOTE:
– By passing MyApp Widget inside the runApp() function, we inform Flutter that MyApp Widget is the entry point of the application and the root of the widget tree of my application.
– Inside MyApp, we are using MaterialApp, which provides a material theme to complete the app. It’s not mandatory, but it’s recommended.


 

Step 4.
Create another dart class, and name it SlidingMenuController.
import "package:flutter/material.dart";

enum SlidingMenuState { closed, open, closing, opening }

class SlidingMenuController extends ChangeNotifier {
 TickerProvider vsync;

 AnimationController _animationController;

 SlidingMenuState state = SlidingMenuState.closed;

 SlidingMenuController({this.vsync}) {
   _animationController = new AnimationController(
       vsync: vsync, duration: Duration(milliseconds: 250));

   _animationController.addListener(() {
     notifyListeners();
   });

   _animationController.addStatusListener((status) {
     switch (status) {
       case AnimationStatus.forward:
         {
           state = SlidingMenuState.opening;
           break;
         }
       case AnimationStatus.reverse:
         {
           state = SlidingMenuState.closing;
           break;
         }
       case AnimationStatus.completed:
         {
           state = SlidingMenuState.open;
           break;
         }
       case AnimationStatus.dismissed:
         {
           state = SlidingMenuState.closed;
           break;
         }
     }
     notifyListeners();
   });
 }

 get percentOpen {
   if (this._animationController == null) {
     return 0.0;
   } else {
     return this._animationController.value;
   }
 }

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

 open() {
   _animationController.forward();
 }

 close() {
   _animationController.reverse();
 }

 toggle() {
   if (state == SlidingMenuState.open) {
     close();
   } else {
     open();
   }
 }
}

This class controls the animation and also notifies the class listeners about the change in the animation value.


Step 5.
Create SlidingMenu class
import 'package:episodie_flutter/SlidingMenuController.dart';
import 'package:episodie_flutter/SlidingMenuDemo.dart';
import "package:flutter/material.dart";

class SlidingMenu extends StatefulWidget {
 final Widget menuWidget;
 final Widget detailWidget;
 final MenuStatusChangeRequester menuStatusChangeRequester;

 SlidingMenu(
     {this.menuWidget, this.detailWidget, this.menuStatusChangeRequester});

 @override
 State<StatefulWidget> createState() {
   return _SlidingMenuState();
 }
}

class _SlidingMenuState extends State<SlidingMenu>
   with TickerProviderStateMixin {
 static const _MAX_SLID_AMOUNT = 278.0;

 SlidingMenuController menuController;

 @override
 void initState() {
   menuController = SlidingMenuController(vsync: this);
   menuController.addListener(() {
     setState(() {});
   });
   this.widget.menuStatusChangeRequester.addListener(() {
     menuController.toggle();
   });
   super.initState();
 }

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

 @override
 Widget build(BuildContext context) {
   return new Stack(
     children: <Widget>[
       this.widget.menuWidget,
       _transformWidget(this.widget.detailWidget)
     ],
   );
 }

 Widget _transformWidget(Widget target) {
   final slideAmount = _MAX_SLID_AMOUNT * menuController.percentOpen;
   final contentScale = 1.0 - (0.2 * menuController.percentOpen);
   final cornerRadius = 10 * menuController.percentOpen;
   final offset = 5.0 * menuController.percentOpen;
   final blurRadius = 20.0 * menuController.percentOpen;
   final spreadRadius = 10.0 * menuController.percentOpen;

   return new Transform(
     transform: new Matrix4.translationValues(slideAmount, 0.0, 0.0)
       ..scale(contentScale, contentScale),
     alignment: Alignment.centerLeft,
     child: new Container(
       child: ClipRRect(
         borderRadius: BorderRadius.circular(cornerRadius),
         child: target,
       ),
       decoration: BoxDecoration(boxShadow: [
         BoxShadow(
             color: Colors.black87,
             offset: Offset(0.0, offset),
             blurRadius: blurRadius,
             spreadRadius: spreadRadius)
       ]),
     ),
   );
 }
}

NOTE:
– This class is our main widget. It basically takes two widgets, which will be placed in a stack.
– Detail Widget will be on the top, and all the transformation occurs on the detail widget.


Step 6.
Create the demo class
import 'package:episodie_flutter/SlidingMenu.dart';
import 'package:flutter/material.dart';

class SlidingMenuDemo extends StatelessWidget {
 MenuStatusChangeRequester menuStatusHandler = MenuStatusChangeRequester();

 @override
 Widget build(BuildContext context) {
   return new SlidingMenu(
     menuWidget: MenuWidget(
       menuStatusChangeRequester: menuStatusHandler,
     ),
     detailWidget: DetailWidget(
       menuStatusChangeRequester: menuStatusHandler,
     ),
     menuStatusChangeRequester: menuStatusHandler,
   );
 }
}

class MenuWidget extends StatelessWidget {
 final MenuStatusChangeRequester menuStatusChangeRequester;

 MenuWidget({this.menuStatusChangeRequester});

 @override
 Widget build(BuildContext context) {
   return new Container(
     color: Colors.red,
   );
 }
}

class DetailWidget extends StatelessWidget {
 final MenuStatusChangeRequester menuStatusChangeRequester;

 DetailWidget({this.menuStatusChangeRequester});

 @override
 Widget build(BuildContext context) {
   return new Scaffold(
     appBar: new AppBar(
       title: Text("DEtails"),
       leading: IconButton(
           icon: Icon(Icons.menu),
           onPressed: () {
             this.menuStatusChangeRequester.askSliderToToggle();
           }),
     ),
     body: new Container(
       color: Colors.yellow,
     ),
   );
 }
}

class MenuStatusChangeRequester extends ChangeNotifier {
 askSliderToToggle() {
   notifyListeners();
 }

 slideBy(percent) {}
}

NOTE:
We have created another Change Notifier here that will be used by the menu and detail widget if you want to toggle the menu.


 

The Complete Process

When a user clicks on the menu icon on the details screen, the detail screen calls the “askSliderToToggle” on “MenuStatusChangeRequester”. The same instance of MenuStausChangeRequester is passed to SlidingMenu and SlidingMenu and added as a listener to MenuStatusChangeRequester. Calling “askSliderToToggle” on “MenuStatusChangeRequester” notifies “Sliding Menu”.

@override
void initState() {
 menuController = SlidingMenuController(vsync: this);
 menuController.addListener(() {
   setState(() {});
 });
 this.widget.menuStatusChangeRequester.addListener(() {
   menuController.toggle();
 });
 super.initState();
}

We are asking our menu controller to perform a toggle operation upon receiving any notification from “MenuStatusChangeRequester”.

Take a look at what happens in our menu controller –

open() {
 _animationController.forward();
}

close() {
 _animationController.reverse();
}

toggle() {
 if (state == SlidingMenuState.open) {
   close();
 } else {
   open();
 }
}

We are starting the animation if the drawer is closed and then reversing it if drawer is open. Our SlidingMenuController is also extended from ChangeNotifier, notifying the listeners about the change in animation values using this code:

_animationController.addListener(() {
 notifyListeners();
});

If you look at the init method of _SlidingMenuState again. _SlidingMenuState is also added as listeners for the SlidingMenuController. Every time it receives the notification, it’s calling it’s “setState” method.

Whenever “setState” is called, Flutter redraws the SlidingMenuWidget if you look into the “transformWidget” method. We are applying some transformations to the detail widget, depending upon the current percentage of animation which gives us the sliding effect.

Stay-Up-To-Date

Keep in the loop with the latest in emerging technology and Mutual Mobile