drawer.dart 5.5 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 39 40 41
      child: new ConstrainedBox(
        constraints: const BoxConstraints.expand(width: _kWidth),
        child: new Material(
          level: route.level,
          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 {
54
  _DrawerRoute({ this.child, this.level });
55 56 57 58

  final Widget child;
  final int level;

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

61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
  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)
    );
  }
Hixie's avatar
Hixie committed
86

87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
  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();
}
117

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

127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147
  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;
148

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

151 152 153 154
  void _handleSizeChanged(Size newSize) {
    setState(() {
      _width = newSize.width;
    });
Hixie's avatar
Hixie committed
155 156
  }

157
  void _handlePointerDown(_) {
Hixie's avatar
Hixie committed
158 159 160
    _performance.stop();
  }

161 162
  void _move(double delta) {
    _performance.progress += delta / _width;
163 164 165 166
  }

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

Hixie's avatar
Hixie committed
175 176
  void _close() {
    _performance.fling(velocity: -1.0);
177
  }
178 179 180 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 209 210 211 212

  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
              )
            )
          )
        )
      ])
    );
  }
213
}
214

Adam Barth's avatar
Adam Barth committed
215 216
void showDrawer({ BuildContext context, Widget child, int level: 3 }) {
  Navigator.of(context).push(new _DrawerRoute(child: child, level: level));
217
}