Commit 60d77d97 authored by Adam Barth's avatar Adam Barth

Factor DrawerController out of Drawer

This patch paves the path for refactoring the Drawer into the Scaffold to
support edge swipes and peeks.
parent ef866e00
...@@ -6,8 +6,7 @@ import 'package:flutter/animation.dart'; ...@@ -6,8 +6,7 @@ import 'package:flutter/animation.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'colors.dart'; import 'colors.dart';
import 'shadows.dart'; import 'material.dart';
import 'theme.dart';
// TODO(eseidel): Draw width should vary based on device size: // TODO(eseidel): Draw width should vary based on device size:
// http://www.google.com/design/spec/layout/structure.html#structure-side-nav // http://www.google.com/design/spec/layout/structure.html#structure-side-nav
...@@ -24,11 +23,7 @@ import 'theme.dart'; ...@@ -24,11 +23,7 @@ import 'theme.dart';
const double _kWidth = 304.0; const double _kWidth = 304.0;
const double _kMinFlingVelocity = 365.0; const double _kMinFlingVelocity = 365.0;
const double _kFlingVelocityScale = 1.0 / 300.0;
const Duration _kBaseSettleDuration = const Duration(milliseconds: 246); 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 { class _Drawer extends StatelessComponent {
_Drawer({ Key key, this.route }) : super(key: key); _Drawer({ Key key, this.route }) : super(key: key);
...@@ -38,101 +33,138 @@ class _Drawer extends StatelessComponent { ...@@ -38,101 +33,138 @@ class _Drawer extends StatelessComponent {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Focus( return new Focus(
key: new GlobalObjectKey(route), key: new GlobalObjectKey(route),
autofocus: true, child: new ConstrainedBox(
child: new GestureDetector( constraints: const BoxConstraints.expand(width: _kWidth),
onHorizontalDragStart: (_) { child: new Material(
if (route.interactive) level: route.level,
route._takeControl();
},
onHorizontalDragUpdate: (double delta) {
if (route.interactive)
route._moveDrawer(delta);
},
onHorizontalDragEnd: (Offset velocity) {
if (route.interactive)
route._settle(velocity);
},
child: new Stack(<Widget>[
// 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<Point>(_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: route.child
) )
) )
)
])
)
); );
} }
}
enum _DrawerState {
showing,
popped,
closed,
} }
class _DrawerRoute extends TransitionRoute { class _DrawerRoute extends OverlayRoute {
_DrawerRoute({ this.child, this.level }); _DrawerRoute({ this.child, this.level });
final Widget child; final Widget child;
final int level; final int level;
Duration get transitionDuration => _kBaseSettleDuration; List<WidgetBuilder> get builders => <WidgetBuilder>[ _build ];
bool get opaque => false;
bool get interactive => _interactive; final GlobalKey<_DrawerControllerState> _drawerKey = new GlobalKey<_DrawerControllerState>();
bool _interactive = true; _DrawerState _state = _DrawerState.showing;
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)
);
}
Performance _performance; 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;
Performance createPerformance() { _DrawerControllerState createState() => new _DrawerControllerState();
_performance = super.createPerformance(); }
return _performance;
class _DrawerControllerState extends State<_DrawerController> {
void initState() {
super.initState();
_performance = new Performance(duration: config.settleDuration)
..addListener(_performanceChanged)
..addStatusListener(_performanceStatusChanged)
..play();
} }
List<WidgetBuilder> get builders => <WidgetBuilder>[ _build ]; void dispose() {
_performance
..removeListener(_performanceChanged)
..removeStatusListener(_performanceStatusChanged)
..stop();
super.dispose();
}
Widget _build(BuildContext context) => new _Drawer(route: this); void _performanceChanged() {
setState(() {
// The performance's state is our build state, and it changed already.
});
}
void didPop([dynamic result]) { void _performanceStatusChanged(PerformanceStatus status) {
assert(result == null); // because we don't do anything with it, so otherwise it'd be lost if (status == PerformanceStatus.dismissed && config.onClosed != null)
super.didPop(result); config.onClosed();
_interactive = false;
} }
void _takeControl() { Performance _performance;
assert(_interactive); double _width;
final AnimatedColorValue _color = new AnimatedColorValue(Colors.transparent, end: Colors.black54);
void _handleSizeChanged(Size newSize) {
setState(() {
_width = newSize.width;
});
}
void _handlePointerDown(_) {
_performance.stop(); _performance.stop();
} }
void _moveDrawer(double delta) { void _move(double delta) {
assert(_interactive); _performance.progress += delta / _width;
_performance.progress += delta / _kWidth;
} }
void _settle(Offset velocity) { void _settle(Offset velocity) {
assert(_interactive);
if (velocity.dx.abs() >= _kMinFlingVelocity) { if (velocity.dx.abs() >= _kMinFlingVelocity) {
_performance.fling(velocity: velocity.dx * _kFlingVelocityScale); _performance.fling(velocity: velocity.dx / _width);
} else if (_performance.progress < 0.5) { } else if (_performance.progress < 0.5) {
_close(); _close();
} else { } else {
...@@ -141,9 +173,43 @@ class _DrawerRoute extends TransitionRoute { ...@@ -141,9 +173,43 @@ class _DrawerRoute extends TransitionRoute {
} }
void _close() { void _close() {
assert(_interactive);
_performance.fling(velocity: -1.0); _performance.fling(velocity: -1.0);
} }
Widget build(BuildContext context) {
_performance.updateVariable(_color);
return new GestureDetector(
onHorizontalDragUpdate: _move,
onHorizontalDragEnd: _settle,
child: new Stack(<Widget>[
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 }) { void showDrawer({ BuildContext context, Widget child, int level: 3 }) {
......
...@@ -197,11 +197,9 @@ class RenderPositionedBox extends RenderShiftedBox { ...@@ -197,11 +197,9 @@ class RenderPositionedBox extends RenderShiftedBox {
if (child != null) { if (child != null) {
child.layout(constraints.loosen(), parentUsesSize: true); child.layout(constraints.loosen(), parentUsesSize: true);
final Size desiredSize = new Size(child.size.width * (_widthFactor ?? 1.0), size = constraints.constrain(new Size(shrinkWrapWidth ? child.size.width * _widthFactor : double.INFINITY,
child.size.height * (_heightFactor ?? 1.0)); shrinkWrapHeight ? child.size.height * _heightFactor : double.INFINITY));
size = constraints.constrain(new Size(shrinkWrapWidth ? desiredSize.width : double.INFINITY, final Offset delta = size - child.size;
shrinkWrapHeight ? desiredSize.height : double.INFINITY));
final Offset delta = size - desiredSize;
final BoxParentData childParentData = child.parentData; final BoxParentData childParentData = child.parentData;
childParentData.position = delta.scale(_alignment.x, _alignment.y).toPoint(); childParentData.position = delta.scale(_alignment.x, _alignment.y).toPoint();
} else { } else {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment