Skip to content

Instantly share code, notes, and snippets.

@a7me63azzab
Created February 22, 2021 11:10
Show Gist options
  • Save a7me63azzab/df8925a8b02a4603e92f1b3c8fe9bdbf to your computer and use it in GitHub Desktop.
Save a7me63azzab/df8925a8b02a4603e92f1b3c8fe9bdbf to your computer and use it in GitHub Desktop.
import 'dart:math' show pi;
import 'dart:ui' as ui show window;
import 'package:easy_localization/easy_localization.dart' as lang;
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
class ZoomDrawerController {
/// callback function to open the drawer
Function open;
/// callback function to close the drawer
Function close;
/// callback function to toggle the drawer
Function toggle;
/// callback function to determine the status of the drawer
Function isOpen;
/// Drawer state notifier
/// opening, closing, open, closed
ValueNotifier<DrawerState> stateNotifier;
}
class ZoomDrawer extends StatefulWidget {
ZoomDrawer({
this.type = 'default',
this.controller,
@required this.menuScreen,
@required this.mainScreen,
this.slideWidth = 275.0,
this.borderRadius = 16.0,
this.angle = -12.0,
this.backgroundColor = Colors.white,
this.showShadow = false,
this.openCurve,
this.lang,
this.closeCurve,
this.duration,
}) : assert(angle <= 0.0 && angle >= -30.0);
// Layout style
final String type;
final String lang;
/// controller to have access to the open/close/toggle function of the drawer
final ZoomDrawerController controller;
/// Screen containing the menu/bottom screen
final Widget menuScreen;
/// Screen containing the main content to display
final Widget mainScreen;
/// Sliding width of the drawer - defaults to 275.0
final double slideWidth;
/// Border radius of the slided content - defaults to 16.0
final double borderRadius;
/// Rotation angle of the drawer - defaults to -12.0
final double angle;
/// Background color of the drawer shadows - defaults to white
final Color backgroundColor;
/// Boolean, whether to show the drawer shadows - defaults to false
final bool showShadow;
/// Drawer slide out curve
final Curve openCurve;
/// Drawer slide in curve
final Curve closeCurve;
/// Drawer Duration
final Duration duration;
@override
_ZoomDrawerState createState() => new _ZoomDrawerState();
/// static function to provide the drawer state
static _ZoomDrawerState of(BuildContext context) {
return context.findAncestorStateOfType<State<ZoomDrawer>>();
}
/// Static function to determine the device text direction RTL/LTR
static bool isRTL() {
return ui.window.locale.languageCode.toLowerCase() == "ar";
}
}
class _ZoomDrawerState extends State<ZoomDrawer>
with SingleTickerProviderStateMixin {
final Curve _scaleDownCurve = Interval(0.0, 0.3, curve: Curves.easeOut);
final Curve _scaleUpCurve = Interval(0.0, 1.0, curve: Curves.easeOut);
final Curve _slideOutCurve = Interval(0.0, 1.0, curve: Curves.easeOut);
final Curve _slideInCurve =
Interval(0.0, 1.0, curve: Curves.easeOut); // Curves.bounceOut
/// check the slide direction
final int _rtlSlide = ZoomDrawer.isRTL() ? -1 : 1;
final bool _rtl = ZoomDrawer.isRTL();
AnimationController _animationController;
DrawerState _state = DrawerState.closed;
double get _percentOpen => _animationController.value;
/// Open drawer
open() {
_animationController.forward();
}
/// Close drawer
close() {
_animationController.reverse();
}
AnimationController get animationController => _animationController;
/// Toggle drawer
toggle() {
if (_state == DrawerState.open) {
close();
} else if (_state == DrawerState.closed) {
open();
}
}
/// check whether drawer is open
bool isOpen() =>
_state == DrawerState.open /* || _state == DrawerState.opening*/;
/// Drawer state
ValueNotifier<DrawerState> stateNotifier;
@override
void initState() {
super.initState();
stateNotifier = ValueNotifier(_state);
/// Initialize the animation controller
/// add status listener to update the menuStatus
_animationController = AnimationController(
vsync: this,
duration: widget.duration is Duration
? widget.duration
: Duration(milliseconds: 250))
..addStatusListener((AnimationStatus status) {
switch (status) {
case AnimationStatus.forward:
_state = DrawerState.opening;
_updateStatusNotifier();
break;
case AnimationStatus.reverse:
_state = DrawerState.closing;
_updateStatusNotifier();
break;
case AnimationStatus.completed:
_state = DrawerState.open;
_updateStatusNotifier();
break;
case AnimationStatus.dismissed:
_state = DrawerState.closed;
_updateStatusNotifier();
break;
}
});
/// assign controller function to the widget methods
if (widget.controller != null) {
widget.controller.open = open;
widget.controller.close = close;
widget.controller.toggle = toggle;
widget.controller.isOpen = isOpen;
widget.controller.stateNotifier = stateNotifier;
}
}
_updateStatusNotifier() {
stateNotifier.value = _state;
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
/// Build the widget based on the animation value
///
/// * [container] is the widget to be displayed
///
/// * [angle] is the the Z rotation angle
///
/// * [scale] is a string to help identify this animation during
/// debugging (used by [toString]).
///
/// * [slide] is the sliding amount of the drawer
///
Widget _zoomAndSlideContent(Widget container,
{double angle, double scale, double slide = 0}) {
var slidePercent, scalePercent;
/// determine current slide percent based on the MenuStatus
switch (_state) {
case DrawerState.closed:
slidePercent = 0.0;
scalePercent = 0.0;
break;
case DrawerState.open:
slidePercent = 1.0;
scalePercent = 1.0;
break;
case DrawerState.opening:
slidePercent =
(widget.openCurve ?? _slideOutCurve).transform(_percentOpen);
scalePercent = _scaleDownCurve.transform(_percentOpen);
break;
case DrawerState.closing:
slidePercent =
(widget.closeCurve ?? _slideInCurve).transform(_percentOpen);
scalePercent = _scaleUpCurve.transform(_percentOpen);
break;
}
/// calculated sliding amount based on the RTL and animation value
final slideAmount = (widget.slideWidth - slide) * slidePercent * _rtlSlide;
/// calculated scale amount based on the provided scale and animation value
final contentScale = (scale ?? 1.0) - (0.2 * scalePercent);
/// calculated radius based on the provided radius and animation value
final cornerRadius = widget.borderRadius * _percentOpen;
/// calculated rotation amount based on the provided angle and animation value
final rotationAngle =
(((angle ?? widget.angle) * pi * _rtlSlide) / 180) * _percentOpen;
return Transform(
transform: Matrix4.translationValues(slideAmount, 0.0, 0.0)
..rotateZ(rotationAngle)
..scale(contentScale, contentScale),
alignment: Alignment.centerLeft,
child: ClipRRect(
borderRadius: BorderRadius.circular(cornerRadius),
child: container,
),
);
}
/*
Container(
decoration: BoxDecoration(
boxShadow: Platform.isAndroid
? [
BoxShadow(
color: Colors.black12,
offset: const Offset(0.0, 5.0),
blurRadius: 15.0,
spreadRadius: 10.0,
),
]
: kElevationToShadow[5],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(cornerRadius),
child: container,
),
)
* */
Widget renderDefault() {
final rightSlide = MediaQuery.of(context).size.width * 0.4;
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
double slide = rightSlide * _animationController.value;
double scale = 1 - (_animationController.value * 0.3);
// double left = (1 - _animationController.value) * rightSlide;
return Stack(
children: [
Directionality(
textDirection:
widget.lang == 'ar' ? TextDirection.ltr : TextDirection.rtl,
child: Scaffold(
backgroundColor: Colors.blueAccent,
body: Transform.translate(
offset: Offset(0, 0),
child: widget.menuScreen,
),
),
),
Transform(
transform: Matrix4.identity()
..translate(widget.lang == 'ar' ? -slide : slide)
..scale(scale),
alignment: Alignment.center,
child: Directionality(
textDirection:
widget.lang == 'ar' ? TextDirection.ltr : TextDirection.rtl,
child: widget.mainScreen),
),
],
);
},
);
}
Widget renderStyle1() {
final slidePercent =
ZoomDrawer.isRTL() ? MediaQuery.of(context).size.width * .1 : 15.0;
return Stack(
children: [
widget.menuScreen,
if (widget.showShadow) ...[
/// Displaying the first shadow
AnimatedBuilder(
animation: _animationController,
builder: (_, w) => _zoomAndSlideContent(w,
angle: (widget.angle == 0.0) ? 0.0 : widget.angle - 8,
scale: .9,
slide: slidePercent * 2),
child: Container(
color: widget.backgroundColor.withAlpha(31),
),
),
/// Displaying the second shadow
AnimatedBuilder(
animation: _animationController,
builder: (_, w) => _zoomAndSlideContent(w,
angle: (widget.angle == 0.0) ? 0.0 : widget.angle - 4.0,
scale: .95,
slide: slidePercent),
child: Container(
color: widget.backgroundColor,
),
)
],
/// Displaying the main screen
AnimatedBuilder(
animation: _animationController,
builder: (_, w) => _zoomAndSlideContent(w),
child: GestureDetector(
child: widget.mainScreen,
onTap: () {
if (_state == DrawerState.open) {
toggle();
}
},
),
),
],
);
}
Widget renderStyle2() {
final rightSlide = MediaQuery.of(context).size.width * 0.6;
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
double slide = rightSlide * _animationController.value;
double scale = 1 - (_animationController.value * 0.3);
double left = (1 - _animationController.value) * rightSlide;
return Stack(
children: [
Scaffold(
backgroundColor: Colors.blueAccent,
body: Transform.translate(
offset: Offset(0, 0),
child: widget.menuScreen,
),
),
Transform(
transform: Matrix4.identity()..translate(slide),
alignment: Alignment.center,
child: widget.mainScreen,
),
],
);
},
);
}
Widget renderStyle3() {
final rightSlide = MediaQuery.of(context).size.width * 0.6;
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
double slide = rightSlide * _animationController.value;
double left = (1 - _animationController.value) * rightSlide;
return Stack(
children: [
Transform(
transform: Matrix4.identity()..translate(slide),
alignment: Alignment.center,
child: widget.mainScreen,
),
Transform.translate(
offset: Offset(-left, 0),
child: Container(
color: Colors.blueAccent,
width: rightSlide,
child: widget.menuScreen),
),
],
);
},
);
}
Widget renderStyle4() {
final rightSlide = MediaQuery.of(context).size.width * 0.6;
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
// double slide = rightSlide * _animationController.value;
double left = (1 - _animationController.value) * rightSlide;
return Stack(
children: [
widget.mainScreen,
Transform.translate(
offset: Offset(-left, 0),
child: Container(
color: Colors.blueAccent,
width: rightSlide,
child: widget.menuScreen),
),
],
);
},
);
}
Widget renderStyle5() {
final rightSlide = MediaQuery.of(context).size.width * 0.6;
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
double slide = rightSlide * _animationController.value;
double scale = 1 - (_animationController.value * 0.3);
double top = _animationController.value * 200;
return Stack(
children: [
Scaffold(
backgroundColor: Colors.blueAccent,
body: Transform.translate(
offset: Offset(0, 0),
child: widget.menuScreen,
),
),
Transform(
transform: Matrix4.identity()
..translate(slide, top)
..scale(scale),
alignment: Alignment.center,
child: widget.mainScreen,
),
],
);
},
);
}
Widget renderStyle6() {
final rightSlide = MediaQuery.of(context).size.width * 0.6;
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
double x = _animationController.value * (rightSlide / 2);
double rotate = _animationController.value * (pi / 4);
return Stack(
children: [
Scaffold(
backgroundColor: Colors.blueAccent,
body: Transform.translate(
offset: Offset(0, 0),
child: widget.menuScreen,
),
),
Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.0009)
..translate(x)
..rotateY(rotate),
alignment: Alignment.centerRight,
child: widget.mainScreen,
),
],
);
},
);
}
Widget renderStyle7() {
final rightSlide = MediaQuery.of(context).size.width * 0.6;
return AnimatedBuilder(
animation: _animationController,
builder: (context, child) {
double x = _animationController.value * (rightSlide / 2);
double scale = 1 - (_animationController.value * 0.3);
double rotate = _animationController.value * (pi / 4);
return Stack(
children: [
Scaffold(
backgroundColor: Colors.blueAccent,
body: Transform.translate(
offset: Offset(0, 0),
child: widget.menuScreen,
),
),
Transform(
transform: Matrix4.identity()
..setEntry(3, 2, 0.0009)
..translate(x)
..scale(scale)
..rotateY(-rotate),
alignment: Alignment.centerRight,
child: widget.mainScreen,
),
],
);
},
);
}
Widget renderLayout() {
switch (widget.type) {
case 'style1':
return renderStyle1();
break;
case 'style2':
return renderStyle2();
break;
case 'style3':
return renderStyle3();
break;
case 'style4':
return renderStyle4();
break;
case 'style5':
return renderStyle5();
break;
case 'style6':
return renderStyle6();
break;
case 'style7':
return renderStyle7();
break;
default:
return renderDefault();
break;
}
}
@override
Widget build(BuildContext context) {
return GestureDetector(
/// Detecting the slide amount to close the drawer in RTL & LTR
onPanUpdate: (details) {
if (_state == DrawerState.open && details.delta.dx < -6 && !_rtl ||
details.delta.dx < 6 && _rtl) {
toggle();
}
},
child: renderLayout(),
);
}
}
/// Drawer State enum
enum DrawerState { opening, closing, open, closed }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment