drawer.dart 5.76 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'package:flutter/animation.dart';
import 'package:flutter/widgets.dart';
7

8
import 'colors.dart';
9
import 'material.dart';
10 11 12 13 14 15 16 17 18 19 20 21 22 23 24

// TODO(eseidel): Draw width should vary based on device size:
// http://www.google.com/design/spec/layout/structure.html#structure-side-nav

// Mobile:
// Width = Screen width − 56 dp
// Maximum width: 320dp
// Maximum width applies only when using a left nav. When using a right nav,
// the panel can cover the full width of the screen.

// Desktop/Tablet:
// Maximum width for a left nav is 400dp.
// The right nav can vary depending on content.

const double _kWidth = 304.0;
Matt Perry's avatar
Matt Perry committed
25
const double _kMinFlingVelocity = 365.0;
26 27
const Duration _kBaseSettleDuration = const Duration(milliseconds: 246);

28
class _Drawer extends StatelessComponent {
Adam Barth's avatar
Adam Barth committed
29
  _Drawer({ Key key, this.route }) : super(key: key);
Hixie's avatar
Hixie committed
30

31
  final _DrawerRoute route;
32

33
  Widget build(BuildContext context) {
Adam Barth's avatar
Adam Barth committed
34 35
    return new Focus(
      key: new GlobalObjectKey(route),
36 37 38
      child: new ConstrainedBox(
        constraints: const BoxConstraints.expand(width: _kWidth),
        child: new Material(
Hans Muller's avatar
Hans Muller committed
39
          elevation: route.elevation,
40 41
          child: route.child
        )
Adam Barth's avatar
Adam Barth committed
42
      )
43
    );
44
  }
45
}
Hixie's avatar
Hixie committed
46

47 48 49 50
enum _DrawerState {
  showing,
  popped,
  closed,
51 52
}

53
class _DrawerRoute extends OverlayRoute {
Hans Muller's avatar
Hans Muller committed
54
  _DrawerRoute({ this.child, this.elevation });
55 56

  final Widget child;
Hans Muller's avatar
Hans Muller committed
57
  final int elevation;
58

59
  List<WidgetBuilder> get builders => <WidgetBuilder>[ _build ];
60

61 62 63 64
  final GlobalKey<_DrawerControllerState> _drawerKey = new GlobalKey<_DrawerControllerState>();
  _DrawerState _state = _DrawerState.showing;

  Widget _build(BuildContext context) {
65 66 67 68 69 70 71 72 73 74 75 76
    return new RepaintBoundary(
      child: 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:
Hixie's avatar
Hixie committed
77
              finished();
78 79 80 81 82 83 84 85
              break;
            case _DrawerState.closed:
              assert(false);
              break;
          }
        },
        child: new _Drawer(route: this)
      )
86 87
    );
  }
Hixie's avatar
Hixie committed
88

89
  void didPop(dynamic result) {
Hixie's avatar
Hixie committed
90 91
    // we don't call the superclass because we want to control the timing of the
    // call to finished().
92 93 94 95 96 97 98 99 100
    switch (_state) {
      case _DrawerState.showing:
        _drawerKey.currentState?._close();
        _state = _DrawerState.popped;
        break;
      case _DrawerState.popped:
        assert(false);
        break;
      case _DrawerState.closed:
Hixie's avatar
Hixie committed
101
        finished();
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
        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();
}
121

122 123 124 125 126 127 128
class _DrawerControllerState extends State<_DrawerController> {
  void initState() {
    super.initState();
    _performance = new Performance(duration: config.settleDuration)
      ..addListener(_performanceChanged)
      ..addStatusListener(_performanceStatusChanged)
      ..play();
129 130
  }

131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
  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;
152

153
  final AnimatedColorValue _color = new AnimatedColorValue(Colors.transparent, end: Colors.black54);
Adam Barth's avatar
Adam Barth committed
154

155 156 157 158
  void _handleSizeChanged(Size newSize) {
    setState(() {
      _width = newSize.width;
    });
Hixie's avatar
Hixie committed
159 160
  }

161
  void _handlePointerDown(_) {
Hixie's avatar
Hixie committed
162 163 164
    _performance.stop();
  }

165 166
  void _move(double delta) {
    _performance.progress += delta / _width;
167 168 169 170
  }

  void _settle(Offset velocity) {
    if (velocity.dx.abs() >= _kMinFlingVelocity) {
171
      _performance.fling(velocity: velocity.dx / _width);
Hixie's avatar
Hixie committed
172 173
    } else if (_performance.progress < 0.5) {
      _close();
174
    } else {
Hixie's avatar
Hixie committed
175
      _performance.fling(velocity: 1.0);
176 177 178
    }
  }

Hixie's avatar
Hixie committed
179 180
  void _close() {
    _performance.fling(velocity: -1.0);
181
  }
182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208

  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,
209
                child: new RepaintBoundary(
210 211
                  child: config.child
                )
212 213 214 215 216 217 218
              )
            )
          )
        )
      ])
    );
  }
219
}
220

Hans Muller's avatar
Hans Muller committed
221 222
void showDrawer({ BuildContext context, Widget child, int elevation: 16 }) {
  Navigator.of(context).push(new _DrawerRoute(child: child, elevation: elevation));
223
}