diff --git a/example/lib/main.dart b/example/lib/main.dart index 4fb85ab..362a4c1 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -30,25 +30,26 @@ class _MyAppState extends State { return MaterialApp( debugShowCheckedModeBanner: false, home: Scaffold( - body: SliderMenuContainer( - appBarColor: Colors.white, - key: _key, - appBarPadding: const EdgeInsets.only(top: 20), - sliderMenuOpenOffset: 250, - appBarHeight: 60, - title: Text( - title, - style: TextStyle(fontSize: 22, fontWeight: FontWeight.w700), - ), - sliderMenu: MenuWidget( - onItemClick: (title) { - _key.currentState.closeDrawer(); - setState(() { - this.title = title; - }); - }, - ), - sliderMain: MainWidget()), + body: Padding( + padding: const EdgeInsets.only(top: 0), + child: SliderMenuContainer( + appBarColor: Colors.white, + key: _key, + sliderMenuOpenSize: 200, + title: Text( + title, + style: TextStyle(fontSize: 22, fontWeight: FontWeight.w700), + ), + sliderMenu: MenuWidget( + onItemClick: (title) { + _key.currentState.closeDrawer(); + setState(() { + this.title = title; + }); + }, + ), + sliderMain: MainWidget()), + ), ), ); } diff --git a/example/lib/main_widget.dart b/example/lib/main_widget.dart index 62c4d92..b51c5ff 100644 --- a/example/lib/main_widget.dart +++ b/example/lib/main_widget.dart @@ -27,8 +27,11 @@ class _MainWidgetState extends State { @override Widget build(BuildContext context) { return Container( + margin: const EdgeInsets.all(20), child: ListView.separated( - padding: const EdgeInsets.symmetric(horizontal: 10), + scrollDirection: Axis.vertical, + // physics: BouncingScrollPhysics(), + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), itemBuilder: (builder, index) { return LimitedBox( maxHeight: 150, diff --git a/lib/flutter_slider_drawer.dart b/lib/flutter_slider_drawer.dart index 2da0bcd..4d65686 100644 --- a/lib/flutter_slider_drawer.dart +++ b/lib/flutter_slider_drawer.dart @@ -1,4 +1,4 @@ library flutter_slider_drawer; -export 'package:flutter_slider_drawer/src/slider_open.dart'; +export 'package:flutter_slider_drawer/src/slider_direction.dart'; export 'package:flutter_slider_drawer/src/slider.dart'; diff --git a/lib/src/app_bar.dart b/lib/src/app_bar.dart new file mode 100644 index 0000000..f1868d5 --- /dev/null +++ b/lib/src/app_bar.dart @@ -0,0 +1,76 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_slider_drawer/src/slider_direction.dart'; + +class SliderAppBar extends StatelessWidget { + final EdgeInsets appBarPadding; + final Color appBarColor; + final Widget drawerIcon; + final Color splashColor; + final Color drawerIconColor; + final double drawerIconSize; + final double appBarHeight; + final AnimationController animationController; + final VoidCallback onTap; + final Widget title; + final bool isTitleCenter; + final Widget trailing; + final SlideDirection slideDirection; + + const SliderAppBar( + {Key key, + this.appBarPadding, + this.appBarColor, + this.drawerIcon, + this.splashColor, + this.drawerIconColor, + this.drawerIconSize, + this.animationController, + this.onTap, + this.title, + this.isTitleCenter, + this.trailing, + this.slideDirection, this.appBarHeight}) + : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + height: appBarHeight, + padding: appBarPadding ?? const EdgeInsets.only(top: 24), + color: appBarColor, + child: Row( + children: appBar(), + ), + ); + } + + List appBar() { + List list = [ + drawerIcon ?? + IconButton( + splashColor: splashColor ?? Colors.black, + icon: AnimatedIcon( + icon: AnimatedIcons.menu_close, + color: drawerIconColor, + size: drawerIconSize, + progress: animationController), + onPressed: () => onTap()), + Expanded( + child: isTitleCenter + ? Center( + child: title, + ) + : title, + ), + trailing ?? + SizedBox( + width: 35, + ) + ]; + + if (slideDirection == SlideDirection.RIGHT_TO_LEFT) { + return list.reversed.toList(); + } + return list; + } +} diff --git a/lib/src/helper/utils.dart b/lib/src/helper/utils.dart new file mode 100644 index 0000000..75de548 --- /dev/null +++ b/lib/src/helper/utils.dart @@ -0,0 +1,36 @@ +import 'dart:ui'; + +import 'package:flutter_slider_drawer/flutter_slider_drawer.dart'; + +class Utils { + /// + /// This method get Offset base on [sliderOpen] type + /// + + static Offset getOffsetValues(SlideDirection direction, double value) { + switch (direction) { + case SlideDirection.LEFT_TO_RIGHT: + return Offset(value, 0); + case SlideDirection.RIGHT_TO_LEFT: + return Offset(-value, 0); + case SlideDirection.TOP_TO_BOTTOM: + return Offset(0, value); + default: + return Offset(value, 0); + } + } + + static Offset getOffsetValueForShadow( + SlideDirection direction, double value, double slideOpenWidth) { + switch (direction) { + case SlideDirection.LEFT_TO_RIGHT: + return Offset(value - (slideOpenWidth > 50 ? 20 : 10), 0); + case SlideDirection.RIGHT_TO_LEFT: + return Offset(-value - 5, 0); + case SlideDirection.TOP_TO_BOTTOM: + return Offset(0, value - (slideOpenWidth > 50 ? 15 : 5)); + default: + return Offset(value - 30.0, 0); + } + } +} diff --git a/lib/src/menu_bar.dart b/lib/src/menu_bar.dart new file mode 100644 index 0000000..744e45f --- /dev/null +++ b/lib/src/menu_bar.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_slider_drawer/src/slider_direction.dart'; + +/// +/// Build and Align the Menu widget based on the slide open type +/// +class SlideMenuBar extends StatelessWidget { + final SlideDirection slideDirection; + final double sliderMenuOpenSize; + final Widget sliderMenu; + + const SlideMenuBar( + {Key key, this.slideDirection, this.sliderMenuOpenSize, this.sliderMenu}) + : super(key: key); + + @override + Widget build(BuildContext context) { + var container = Container( + width: sliderMenuOpenSize, + child: sliderMenu, + ); + switch (slideDirection) { + case SlideDirection.LEFT_TO_RIGHT: + return container; + break; + case SlideDirection.RIGHT_TO_LEFT: + return Positioned(right: 0, top: 0, bottom: 0, child: container); + case SlideDirection.TOP_TO_BOTTOM: + return Positioned(right: 0, left: 0, top: 0, child: container); + break; + } + return Container(); + } +} diff --git a/lib/src/slider.dart b/lib/src/slider.dart index bd73e21..900e51f 100644 --- a/lib/src/slider.dart +++ b/lib/src/slider.dart @@ -1,13 +1,19 @@ +import 'package:flutter/animation.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_slider_drawer/flutter_slider_drawer.dart'; +import 'package:flutter_slider_drawer/src/app_bar.dart'; +import 'package:flutter_slider_drawer/src/menu_bar.dart'; +import 'package:flutter_slider_drawer/src/helper/utils.dart'; +import 'package:flutter_slider_drawer/src/slider_direction.dart'; class SliderMenuContainer extends StatefulWidget { final Widget sliderMenu; final Widget sliderMain; - final int sliderAnimationTimeInMilliseconds; - final double sliderMenuOpenOffset; - final double sliderMenuCloseOffset; + final int animationDuration; + final double sliderMenuOpenSize; + final double sliderMenuCloseSize; + final bool isDraggable; + final bool hasAppBar; final Color drawerIconColor; final Widget drawerIcon; final double drawerIconSize; @@ -23,14 +29,15 @@ class SliderMenuContainer extends StatefulWidget { final Widget trailing; final Color appBarColor; final EdgeInsets appBarPadding; - final SliderOpen sliderOpen; + final SlideDirection slideDirection; const SliderMenuContainer({ Key key, this.sliderMenu, this.sliderMain, - this.sliderAnimationTimeInMilliseconds = 200, - this.sliderMenuOpenOffset = 265, + this.isDraggable = true, + this.animationDuration = 200, + this.sliderMenuOpenSize = 265, this.drawerIconColor = Colors.black, this.drawerIcon, this.splashColor, @@ -40,13 +47,14 @@ class SliderMenuContainer extends StatefulWidget { this.appBarPadding, this.title, this.drawerIconSize = 27, - this.appBarHeight, - this.sliderMenuCloseOffset = 0, - this.sliderOpen = SliderOpen.LEFT_TO_RIGHT, + this.appBarHeight = 70, + this.sliderMenuCloseSize = 0, + this.slideDirection = SlideDirection.LEFT_TO_RIGHT, this.isShadow = false, this.shadowColor = Colors.grey, this.shadowBlurRadius = 25.0, this.shadowSpreadRadius = 5.0, + this.hasAppBar = true, }) : assert(sliderMenu != null), assert(sliderMain != null), super(key: key); @@ -56,236 +64,232 @@ class SliderMenuContainer extends StatefulWidget { } class SliderMenuContainerState extends State - with SingleTickerProviderStateMixin { - double _slideBarXOffset = 0; - double _slideBarYOffset = 0; - bool _isSlideBarOpen = false; - AnimationController _animationController; + with TickerProviderStateMixin { + static const double WIDTH_GESTURE = 50.0; + static const double HEIGHT_GESTURE = 30.0; + static const double BLUR_SHADOW = 20.0; + double slideAmount = 0.0; + double _percent = 0.0; + + AnimationController _animationDrawerController; + Animation animation; + + bool dragging = false; Widget drawerIcon; /// check whether drawer is open - bool get isDrawerOpen => _isSlideBarOpen; + bool get isDrawerOpen => _animationDrawerController.isCompleted; - /// Toggle drawer - void toggle() { - setState(() { - _isSlideBarOpen - ? _animationController.reverse() - : _animationController.forward(); - if (widget.sliderOpen == SliderOpen.LEFT_TO_RIGHT || - widget.sliderOpen == SliderOpen.RIGHT_TO_LEFT) { - _slideBarXOffset = _isSlideBarOpen - ? widget.sliderMenuCloseOffset - : widget.sliderMenuOpenOffset; - } else { - _slideBarYOffset = _isSlideBarOpen - ? widget.sliderMenuCloseOffset - : widget.sliderMenuOpenOffset; - } + /// it's provide [animationController] for handle and lister drawer animation + AnimationController get animationController => _animationDrawerController; - _isSlideBarOpen = !_isSlideBarOpen; - }); - } + /// Toggle drawer + void toggle() => _animationDrawerController.isCompleted + ? _animationDrawerController.reverse() + : _animationDrawerController.forward(); /// Open drawer - void openDrawer() { - setState(() { - _animationController.forward(); - if (widget.sliderOpen == SliderOpen.LEFT_TO_RIGHT || - widget.sliderOpen == SliderOpen.RIGHT_TO_LEFT) { - _slideBarXOffset = widget.sliderMenuOpenOffset; - } else { - _slideBarYOffset = widget.sliderMenuOpenOffset; - } - _isSlideBarOpen = true; - }); - } + void openDrawer() => _animationDrawerController.forward(); /// Close drawer - void closeDrawer() { - setState(() { - _animationController.reverse(); - if (widget.sliderOpen == SliderOpen.LEFT_TO_RIGHT || - widget.sliderOpen == SliderOpen.RIGHT_TO_LEFT) { - _slideBarXOffset = widget.sliderMenuCloseOffset; - } else { - _slideBarYOffset = widget.sliderMenuCloseOffset; - } - _isSlideBarOpen = false; - }); - } + void closeDrawer() => _animationDrawerController.reverse(); @override void initState() { super.initState(); - _animationController = AnimationController( + _animationDrawerController = AnimationController( vsync: this, - duration: - Duration(milliseconds: widget.sliderAnimationTimeInMilliseconds)); + duration: Duration(milliseconds: widget.animationDuration)); + + animation = Tween( + begin: widget.sliderMenuCloseSize, end: widget.sliderMenuOpenSize) + .animate(CurvedAnimation( + parent: _animationDrawerController, + curve: Curves.easeIn, + reverseCurve: Curves.easeOut)); } @override Widget build(BuildContext context) { - return Container( - child: Stack(children: [ - /// Display Menu - menuWidget(), + return LayoutBuilder(builder: (context, constrain) { + return Container( + child: Stack(children: [ + /// Display Menu + SlideMenuBar( + slideDirection: widget.slideDirection, + sliderMenu: widget.sliderMenu, + sliderMenuOpenSize: widget.sliderMenuOpenSize, + ), + + /// Displaying the shadow + if (widget.isShadow) ...[ + AnimatedBuilder( + animation: _animationDrawerController, + builder: (_, child) { + return Transform.translate( + offset: Utils.getOffsetValueForShadow(widget.slideDirection, + animation.value, widget.sliderMenuOpenSize), + child: child, + ); + return child; + }, + child: Container( + width: double.infinity, + height: double.infinity, + decoration: BoxDecoration(shape: BoxShape.rectangle, boxShadow: [ + BoxShadow( + color: widget.shadowColor, + blurRadius: widget.shadowBlurRadius, + // soften the shadow + spreadRadius: widget.shadowSpreadRadius, + //extend the shadow + offset: Offset( + 15.0, // Move to right 15 horizontally + 15.0, // Move to bottom 15 Vertically + ), + ) + ]), + ), + ), + ], - /// Displaying the shadow - if (widget.isShadow) ...[ - AnimatedContainer( - duration: - Duration(milliseconds: widget.sliderAnimationTimeInMilliseconds), - curve: Curves.easeIn, - width: double.infinity, - height: double.infinity, - transform: getTranslationValuesForShadow(widget.sliderOpen), - decoration: BoxDecoration(shape: BoxShape.rectangle, boxShadow: [ - BoxShadow( - color: widget.shadowColor, - blurRadius: widget.shadowBlurRadius, // soften the shadow - spreadRadius: widget.shadowSpreadRadius, //extend the shadow - offset: Offset( - 15.0, // Move to right 10 horizontally - 15.0, // Move to bottom 10 Vertically + //Display Main Screen + AnimatedBuilder( + animation: _animationDrawerController, + builder: (_, child) { + return Transform.translate( + offset: + Utils.getOffsetValues(widget.slideDirection, animation.value), + child: child, + ); + }, + child: GestureDetector( + behavior: HitTestBehavior.deferToChild, + onHorizontalDragStart: _onHorizontalDragStart, + onHorizontalDragEnd: _onHorizontalDragEnd, + onHorizontalDragUpdate: (detail) => + _onHorizontalDragUpdate(detail, constrain), + child: Container( + width: double.infinity, + height: double.infinity, + color: widget.appBarColor, + child: Column( + children: [ + if (widget.hasAppBar) + SliderAppBar( + slideDirection: widget.slideDirection, + onTap: () => toggle(), + appBarHeight: widget.appBarHeight, + animationController: _animationDrawerController, + appBarColor: widget.appBarColor, + appBarPadding: widget.appBarPadding, + drawerIcon: widget.drawerIcon, + drawerIconColor: widget.drawerIconColor, + drawerIconSize: widget.drawerIconSize, + isTitleCenter: widget.isTitleCenter, + splashColor: widget.splashColor, + title: widget.title ?? '', + trailing: widget.trailing, + ), + Expanded(child: widget.sliderMain), + ], ), - ) - ]), + ), + ), ), - ], + ])); + }); + } - /// Display Main Screen - AnimatedContainer( - duration: - Duration(milliseconds: widget.sliderAnimationTimeInMilliseconds), - curve: Curves.easeIn, - width: double.infinity, - height: double.infinity, - color: widget.appBarColor, - transform: getTranslationValues(widget.sliderOpen), - child: Column( - children: [ - Container( - padding: widget.appBarPadding ?? const EdgeInsets.only(top: 24), - color: widget.appBarColor, - child: Row( - children: appBar(), - ), - ), - Expanded(child: widget.sliderMain), - ], - )), - ])); + @override + void dispose() { + super.dispose(); + _animationDrawerController.dispose(); } - List appBar() { - List list = [ - widget.drawerIcon ?? - IconButton( - splashColor: widget.splashColor ?? Colors.black, - icon: AnimatedIcon( - icon: AnimatedIcons.menu_close, - color: widget.drawerIconColor, - size: widget.drawerIconSize, - progress: _animationController), - onPressed: () { - toggle(); - }), - Expanded( - child: widget.isTitleCenter - ? Center( - child: widget.title, - ) - : widget.title, - ), - widget.trailing ?? - SizedBox( - width: 35, - ) - ]; + void _onHorizontalDragStart(DragStartDetails detail) { + if (!widget.isDraggable) return; - if (widget.sliderOpen == SliderOpen.RIGHT_TO_LEFT) { - return list.reversed.toList(); + //Check use start dragging from left edge / right edge then enable dragging + if ((widget.slideDirection == SlideDirection.LEFT_TO_RIGHT && + detail.localPosition.dx <= WIDTH_GESTURE) || + (widget.slideDirection == SlideDirection.RIGHT_TO_LEFT && + detail.localPosition.dx >= + WIDTH_GESTURE) /*&& + detail.localPosition.dy <= widget.appBarHeight*/ + ) { + this.setState(() { + dragging = true; + }); + } + //Check use start dragging from top edge / bottom edge then enable dragging + if (widget.slideDirection == SlideDirection.TOP_TO_BOTTOM && + detail.localPosition.dy >= HEIGHT_GESTURE) { + this.setState(() { + dragging = true; + }); } - return list; } - /// Build and Align the Menu widget based on the slide open type - menuWidget() { - switch (widget.sliderOpen) { - case SliderOpen.LEFT_TO_RIGHT: - return Container( - width: widget.sliderMenuOpenOffset, - child: widget.sliderMenu, - ); - break; - case SliderOpen.RIGHT_TO_LEFT: - return Positioned( - right: 0, - top: 0, - bottom: 0, - child: Container( - width: widget.sliderMenuOpenOffset, - child: widget.sliderMenu, - ), - ); - case SliderOpen.TOP_TO_BOTTOM: - return Positioned( - right: 0, - left: 0, - top: 0, - child: Container( - width: widget.sliderMenuOpenOffset, - child: widget.sliderMenu, - ), - ); - break; + void _onHorizontalDragEnd(DragEndDetails detail) { + if (!widget.isDraggable) return; + if (dragging) { + openOrClose(); + setState(() { + dragging = false; + }); } } - /// - /// This method get Matrix4 data base on [sliderOpen] type - /// - - Matrix4 getTranslationValues(SliderOpen sliderOpen) { - switch (sliderOpen) { - case SliderOpen.LEFT_TO_RIGHT: - return Matrix4.translationValues( - _slideBarXOffset, _slideBarYOffset, 1.0); - case SliderOpen.RIGHT_TO_LEFT: - return Matrix4.translationValues( - -_slideBarXOffset, _slideBarYOffset, 1.0); - - case SliderOpen.TOP_TO_BOTTOM: - return Matrix4.translationValues(0, _slideBarYOffset, 1.0); + void _onHorizontalDragUpdate( + DragUpdateDetails detail, + BoxConstraints constraints, + ) { + if (!widget.isDraggable) return; + // open drawer for left/right type drawer + if (dragging && widget.slideDirection == SlideDirection.LEFT_TO_RIGHT || + widget.slideDirection == SlideDirection.RIGHT_TO_LEFT) { + var globalPosition = detail.globalPosition.dx; + globalPosition = globalPosition < 0 ? 0 : globalPosition; + double position = globalPosition / constraints.maxWidth; + var realPosition = widget.slideDirection == SlideDirection.LEFT_TO_RIGHT + ? position + : (1 - position); + move(realPosition); + } + // open drawer for top/bottom type drawer + /*if (dragging && widget.slideDirection == SlideDirection.TOP_TO_BOTTOM) { + var globalPosition = detail.globalPosition.dx; + globalPosition = globalPosition < 0 ? 0 : globalPosition; + double position = globalPosition / constraints.maxHeight; + var realPosition = widget.slideDirection == SlideDirection.TOP_TO_BOTTOM + ? position + : (1 - position); + move(realPosition); + }*/ - default: - return Matrix4.translationValues(0, 0, 1.0); + // close drawer for left/right type drawer + if (isDrawerOpen && + (widget.slideDirection == SlideDirection.LEFT_TO_RIGHT || + widget.slideDirection == SlideDirection.RIGHT_TO_LEFT) && + detail.delta.dx < 15) { + closeDrawer(); } } - Matrix4 getTranslationValuesForShadow(SliderOpen sliderOpen) { - switch (sliderOpen) { - case SliderOpen.LEFT_TO_RIGHT: - return Matrix4.translationValues( - _slideBarXOffset - 30, _slideBarYOffset, 1.0); - case SliderOpen.RIGHT_TO_LEFT: - return Matrix4.translationValues( - -_slideBarXOffset - 5, _slideBarYOffset, 1.0); - - case SliderOpen.TOP_TO_BOTTOM: - return Matrix4.translationValues(0, _slideBarYOffset - 20, 1.0); - - default: - return Matrix4.translationValues(0, 0, 1.0); - } + move(double percent) { + _percent = percent; + _animationDrawerController.value = percent; + _animationDrawerController.notifyListeners(); } - @override - void dispose() { - super.dispose(); - _animationController.dispose(); + openOrClose() { + if (_percent > 0.3) { + openDrawer(); + } else { + closeDrawer(); + } } } diff --git a/lib/src/slider_direction.dart b/lib/src/slider_direction.dart new file mode 100644 index 0000000..7d5d1b3 --- /dev/null +++ b/lib/src/slider_direction.dart @@ -0,0 +1 @@ +enum SlideDirection { LEFT_TO_RIGHT, RIGHT_TO_LEFT, TOP_TO_BOTTOM } diff --git a/lib/src/slider_open.dart b/lib/src/slider_open.dart deleted file mode 100644 index 9fa7f9f..0000000 --- a/lib/src/slider_open.dart +++ /dev/null @@ -1 +0,0 @@ -enum SliderOpen { LEFT_TO_RIGHT, RIGHT_TO_LEFT, TOP_TO_BOTTOM }