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';
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(<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: new ConstrainedBox(
constraints: const BoxConstraints.expand(width: _kWidth),
child: new Material(
level: route.level,
child: route.child
)
)
)
])
)
);
}
}
enum _DrawerState {
showing,
popped,
closed,
}
class _DrawerRoute extends TransitionRoute {
class _DrawerRoute extends OverlayRoute {
_DrawerRoute({ this.child, this.level });
final Widget child;
final int level;
Duration get transitionDuration => _kBaseSettleDuration;
bool get opaque => false;
List<WidgetBuilder> get builders => <WidgetBuilder>[ _build ];
bool get interactive => _interactive;
bool _interactive = true;
final GlobalKey<_DrawerControllerState> _drawerKey = new GlobalKey<_DrawerControllerState>();
_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() {
_performance = super.createPerformance();
return _performance;
_DrawerControllerState createState() => new _DrawerControllerState();
}
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]) {
assert(result == null); // because we don't do anything with it, so otherwise it'd be lost
super.didPop(result);
_interactive = false;
void _performanceStatusChanged(PerformanceStatus status) {
if (status == PerformanceStatus.dismissed && config.onClosed != null)
config.onClosed();
}
void _takeControl() {
assert(_interactive);
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(<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 }) {
......
......@@ -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 {
......
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