Sliding Menu Demo Flutter

hitender pannu
5 min readMar 18, 2021

--

Hi Everyone, Flutter is the new buzz in the frontend world these days and If you have worked with it I am pretty sure you know how it improves the productivity.

Below are the 5 things that we are going to use to create the sliding menu.

Stateless Widget: As the name suggest its a widget without any state. This type of widgets is useful in case if that data we want to show in the widget is not going to change e.g Appbar etc.

Stateful Widget: As the name suggest its a widget with a state. This type of widgets is useful in cases where the data that 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 in forward or reverse or stop that 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 Ticker object. “Ticker” is an object that knows how to register itself with the “SchedulerBinding” and fires a callback every frame. Because of which any animation controller does not perform any calculation between two frames.

Change Notifier: This is a class that is used for change notifications. Its kind of observers in java.

Let’s get started
Step1. Create A new Flutter project.
Step2. Go to projectName/lib/main.dart and delete everything.
Step3. 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:
1. By passing MyApp Widget inside runApp() function, We are telling Flutter that MyApp Widget is the entry point of my application and root of the widget tree of my application.
2. Inside MyApp we are using MaterialApp which provides a material theme to complete app. It's not mandatory but its recommended.

Step4: 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 part and also notifies the listeners of this class about the change in the animation value.

Step5: 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:
1. This class is our main widget it basically takes two widgets which will be placed in a stack.
2. Detail Widget will be coming on the top and all the transformation will be done on the detail widget.

Step 6: Let’s Create our 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 they want to toggle the menu.

COMPLETE PROCESS
When a user clicks on the menu icon on the details screen. Detail screen calls the “askSliderToToggle” on “MenuStatusChangeRequester”.
The same instance of MenuStausChangeRequester is passed to SlidingMenu and SlidingMenu is added as a listener to MenuStatusChangeRequester.
So, calling “askSliderToToggle” on “MenuStatusChangeRequester” notifies “Sliding Menu”.
If you look into the SlidingMenu class

@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 toggle operation on receiving any notification from “MenuStatusChangeRequester”.

Let's 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 in case if the drawer is closed and reversing it incase drawer is open. Our SlidingMenuController is also extended from ChangeNotifier and we are notifying the listeners about the change in animation values using below line of code.

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

If you look at the init method of _SlidingMenuState again. _SlidingMenuState is also added as listeners for the SlidingMenuController and every time it receives the notification it's calling its “setState” method.

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

Thank you for your time.

--

--