From 60d77d972fe883a84af0dad40aebb71a2a90aaeb Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Thu, 5 Nov 2015 16:34:17 -0800 Subject: [PATCH] Factor DrawerController out of Drawer This patch paves the path for refactoring the Drawer into the Scaffold to support edge swipes and peeks. --- packages/flutter/lib/src/material/drawer.dart | 226 +++++++++++------- .../lib/src/rendering/shifted_box.dart | 8 +- 2 files changed, 149 insertions(+), 85 deletions(-) diff --git a/packages/flutter/lib/src/material/drawer.dart b/packages/flutter/lib/src/material/drawer.dart index 2cb4347570..29d1aa59b0 100644 --- a/packages/flutter/lib/src/material/drawer.dart +++ b/packages/flutter/lib/src/material/drawer.dart @@ -6,8 +6,7 @@ import 'package:flutter/animation.dart'; import 'package:flutter/widgets.dart'; import 'colors.dart'; -import 'shadows.dart'; -import 'theme.dart'; +import 'material.dart'; // TODO(eseidel): Draw width should vary based on device size: // http://www.google.com/design/spec/layout/structure.html#structure-side-nav @@ -24,11 +23,7 @@ import 'theme.dart'; const double _kWidth = 304.0; const double _kMinFlingVelocity = 365.0; -const double _kFlingVelocityScale = 1.0 / 300.0; const Duration _kBaseSettleDuration = const Duration(milliseconds: 246); -const Duration _kThemeChangeDuration = const Duration(milliseconds: 200); -const Point _kOpenPosition = Point.origin; -const Point _kClosedPosition = const Point(-_kWidth, 0.0); class _Drawer extends StatelessComponent { _Drawer({ Key key, this.route }) : super(key: key); @@ -38,101 +33,138 @@ class _Drawer extends StatelessComponent { Widget build(BuildContext context) { return new Focus( key: new GlobalObjectKey(route), - autofocus: true, - child: new GestureDetector( - onHorizontalDragStart: (_) { - if (route.interactive) - route._takeControl(); - }, - onHorizontalDragUpdate: (double delta) { - if (route.interactive) - route._moveDrawer(delta); - }, - onHorizontalDragEnd: (Offset velocity) { - if (route.interactive) - route._settle(velocity); - }, - child: new Stack([ - // mask - new GestureDetector( - onTap: () { - if (route.interactive) - route._close(); - }, - child: new ColorTransition( - performance: route.performance, - color: new AnimatedColorValue(Colors.transparent, end: Colors.black54), - child: new Container() - ) - ), - new Positioned( - top: 0.0, - left: 0.0, - bottom: 0.0, - child: new SlideTransition( - performance: route.performance, - position: new AnimatedValue(_kClosedPosition, end: _kOpenPosition), - child: new AnimatedContainer( - curve: Curves.ease, - duration: _kThemeChangeDuration, - decoration: new BoxDecoration( - backgroundColor: Theme.of(context).canvasColor, - boxShadow: shadows[route.level]), - width: _kWidth, - child: route.child - ) - ) - ) - ]) + child: new ConstrainedBox( + constraints: const BoxConstraints.expand(width: _kWidth), + child: new Material( + level: route.level, + child: route.child + ) ) ); } - } -class _DrawerRoute extends TransitionRoute { +enum _DrawerState { + showing, + popped, + closed, +} + +class _DrawerRoute extends OverlayRoute { _DrawerRoute({ this.child, this.level }); final Widget child; final int level; - Duration get transitionDuration => _kBaseSettleDuration; - bool get opaque => false; - - bool get interactive => _interactive; - bool _interactive = true; - - Performance _performance; - - Performance createPerformance() { - _performance = super.createPerformance(); - return _performance; - } - List get builders => [ _build ]; - Widget _build(BuildContext context) => new _Drawer(route: this); + final GlobalKey<_DrawerControllerState> _drawerKey = new GlobalKey<_DrawerControllerState>(); + _DrawerState _state = _DrawerState.showing; - void didPop([dynamic result]) { - assert(result == null); // because we don't do anything with it, so otherwise it'd be lost - super.didPop(result); - _interactive = false; + Widget _build(BuildContext context) { + return new _DrawerController( + key: _drawerKey, + settleDuration: _kBaseSettleDuration, + onClosed: () { + _DrawerState previousState = _state; + _state = _DrawerState.closed; + switch (previousState) { + case _DrawerState.showing: + Navigator.of(context).pop(); + break; + case _DrawerState.popped: + super.didPop(null); + break; + case _DrawerState.closed: + assert(false); + break; + } + }, + child: new _Drawer(route: this) + ); } - void _takeControl() { - assert(_interactive); + void didPop(dynamic result) { + switch (_state) { + case _DrawerState.showing: + _drawerKey.currentState?._close(); + _state = _DrawerState.popped; + break; + case _DrawerState.popped: + assert(false); + break; + case _DrawerState.closed: + super.didPop(null); + break; + } + } +} + +class _DrawerController extends StatefulComponent { + _DrawerController({ + Key key, + this.settleDuration, + this.onClosed, + this.child + }) : super(key: key); + + final Duration settleDuration; + final Widget child; + final VoidCallback onClosed; + + _DrawerControllerState createState() => new _DrawerControllerState(); +} + +class _DrawerControllerState extends State<_DrawerController> { + void initState() { + super.initState(); + _performance = new Performance(duration: config.settleDuration) + ..addListener(_performanceChanged) + ..addStatusListener(_performanceStatusChanged) + ..play(); + } + + void dispose() { + _performance + ..removeListener(_performanceChanged) + ..removeStatusListener(_performanceStatusChanged) + ..stop(); + super.dispose(); + } + + void _performanceChanged() { + setState(() { + // The performance's state is our build state, and it changed already. + }); + } + + void _performanceStatusChanged(PerformanceStatus status) { + if (status == PerformanceStatus.dismissed && config.onClosed != null) + config.onClosed(); + } + + Performance _performance; + double _width; + + final AnimatedColorValue _color = new AnimatedColorValue(Colors.transparent, end: Colors.black54); + + void _handleSizeChanged(Size newSize) { + setState(() { + _width = newSize.width; + }); + } + + void _handlePointerDown(_) { _performance.stop(); } - void _moveDrawer(double delta) { - assert(_interactive); - _performance.progress += delta / _kWidth; + void _move(double delta) { + _performance.progress += delta / _width; } void _settle(Offset velocity) { - assert(_interactive); if (velocity.dx.abs() >= _kMinFlingVelocity) { - _performance.fling(velocity: velocity.dx * _kFlingVelocityScale); + _performance.fling(velocity: velocity.dx / _width); } else if (_performance.progress < 0.5) { _close(); } else { @@ -141,9 +173,43 @@ class _DrawerRoute extends TransitionRoute { } void _close() { - assert(_interactive); _performance.fling(velocity: -1.0); } + + Widget build(BuildContext context) { + _performance.updateVariable(_color); + return new GestureDetector( + onHorizontalDragUpdate: _move, + onHorizontalDragEnd: _settle, + child: new Stack([ + new GestureDetector( + onTap: _close, + child: new DecoratedBox( + decoration: new BoxDecoration( + backgroundColor: _color.value + ), + child: new Container() + ) + ), + new Positioned( + top: 0.0, + left: 0.0, + bottom: 0.0, + child: new Listener( + onPointerDown: _handlePointerDown, + child: new Align( + alignment: const FractionalOffset(1.0, 0.5), + widthFactor: _performance.progress, + child: new SizeObserver( + onSizeChanged: _handleSizeChanged, + child: config.child + ) + ) + ) + ) + ]) + ); + } } void showDrawer({ BuildContext context, Widget child, int level: 3 }) { diff --git a/packages/flutter/lib/src/rendering/shifted_box.dart b/packages/flutter/lib/src/rendering/shifted_box.dart index d4eb6af0cb..f4547f15a3 100644 --- a/packages/flutter/lib/src/rendering/shifted_box.dart +++ b/packages/flutter/lib/src/rendering/shifted_box.dart @@ -197,11 +197,9 @@ class RenderPositionedBox extends RenderShiftedBox { if (child != null) { child.layout(constraints.loosen(), parentUsesSize: true); - final Size desiredSize = new Size(child.size.width * (_widthFactor ?? 1.0), - child.size.height * (_heightFactor ?? 1.0)); - size = constraints.constrain(new Size(shrinkWrapWidth ? desiredSize.width : double.INFINITY, - shrinkWrapHeight ? desiredSize.height : double.INFINITY)); - final Offset delta = size - desiredSize; + size = constraints.constrain(new Size(shrinkWrapWidth ? child.size.width * _widthFactor : double.INFINITY, + shrinkWrapHeight ? child.size.height * _heightFactor : double.INFINITY)); + final Offset delta = size - child.size; final BoxParentData childParentData = child.parentData; childParentData.position = delta.scale(_alignment.x, _alignment.y).toPoint(); } else {