drawer.dart 5.62 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;
25
const double _kEdgeDragWidth = 20.0;
Matt Perry's avatar
Matt Perry committed
26
const double _kMinFlingVelocity = 365.0;
27 28
const Duration _kBaseSettleDuration = const Duration(milliseconds: 246);

29 30 31 32 33 34
class Drawer extends StatelessComponent {
  Drawer({
    Key key,
    this.elevation: 16,
    this.child
  }) : super(key: key);
35

Hans Muller's avatar
Hans Muller committed
36
  final int elevation;
37
  final Widget child;
38

39 40 41 42 43 44
  Widget build(BuildContext context) {
    return new ConstrainedBox(
      constraints: const BoxConstraints.expand(width: _kWidth),
      child: new Material(
        elevation: elevation,
        child: child
45
      )
46 47 48 49
    );
  }
}

50 51 52
class DrawerController extends StatefulComponent {
  DrawerController({
    GlobalKey key,
53 54 55 56 57
    this.child
  }) : super(key: key);

  final Widget child;

58
  DrawerControllerState createState() => new DrawerControllerState();
59
}
60

61
class DrawerControllerState extends State<DrawerController> {
62 63
  void initState() {
    super.initState();
64
    _performance = new Performance(duration: _kBaseSettleDuration)
65
      ..addListener(_performanceChanged)
66
      ..addStatusListener(_performanceStatusChanged);
67 68
  }

69 70 71 72 73 74 75 76 77 78 79 80 81 82
  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.
    });
  }

83 84 85 86 87 88 89 90 91 92 93 94
  LocalHistoryEntry _historyEntry;

  void _ensureHistoryEntry() {
    if (_historyEntry == null) {
      ModalRoute route = ModalRoute.of(context);
      if (route != null) {
        _historyEntry = new LocalHistoryEntry(onRemove: _handleHistoryEntryRemoved);
        route.addLocalHistoryEntry(_historyEntry);
      }
    }
  }

95
  void _performanceStatusChanged(PerformanceStatus status) {
96 97 98 99 100 101 102 103 104 105 106 107 108
    switch (status) {
      case PerformanceStatus.forward:
        _ensureHistoryEntry();
        break;
      case PerformanceStatus.reverse:
        _historyEntry?.remove();
        _historyEntry = null;
        break;
      case PerformanceStatus.dismissed:
        break;
      case PerformanceStatus.completed:
        break;
    }
109 110
  }

111 112 113 114
  void _handleHistoryEntryRemoved() {
    _historyEntry = null;
    close();
  }
115

116 117
  Performance _performance;
  double _width = _kEdgeDragWidth;
Adam Barth's avatar
Adam Barth committed
118

119 120 121 122
  void _handleSizeChanged(Size newSize) {
    setState(() {
      _width = newSize.width;
    });
Hixie's avatar
Hixie committed
123 124
  }

125
  void _handlePointerDown(_) {
Hixie's avatar
Hixie committed
126
    _performance.stop();
127
    _ensureHistoryEntry();
Hixie's avatar
Hixie committed
128 129
  }

130 131
  void _move(double delta) {
    _performance.progress += delta / _width;
132 133 134
  }

  void _settle(Offset velocity) {
135 136
    if (_performance.isDismissed)
      return;
137
    if (velocity.dx.abs() >= _kMinFlingVelocity) {
138
      _performance.fling(velocity: velocity.dx / _width);
Hixie's avatar
Hixie committed
139
    } else if (_performance.progress < 0.5) {
140
      close();
141
    } else {
142
      open();
143 144 145
    }
  }

146 147 148 149 150
  void open() {
    _performance.fling(velocity: 1.0);
  }

  void close() {
Hixie's avatar
Hixie committed
151
    _performance.fling(velocity: -1.0);
152
  }
153

154
  final AnimatedColorValue _color = new AnimatedColorValue(Colors.transparent, end: Colors.black54);
155
  final GlobalKey _gestureDetectorKey = new GlobalKey();
156

157
  Widget build(BuildContext context) {
158
    if (_performance.status == PerformanceStatus.dismissed) {
159
      return new Align(
160
        alignment: const FractionalOffset(0.0, 0.5),
161 162 163 164 165 166 167
        child: new GestureDetector(
          key: _gestureDetectorKey,
          onHorizontalDragUpdate: _move,
          onHorizontalDragEnd: _settle,
          behavior: HitTestBehavior.translucent,
          child: new Container(width: _kEdgeDragWidth)
        )
168 169 170
      );
    } else {
      _performance.updateVariable(_color);
171 172 173 174 175
      return new GestureDetector(
        key: _gestureDetectorKey,
        onHorizontalDragUpdate: _move,
        onHorizontalDragEnd: _settle,
        child: new RepaintBoundary(
176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200
          child: new Stack(
            children: <Widget>[
              new GestureDetector(
                onTap: close,
                child: new DecoratedBox(
                  decoration: new BoxDecoration(
                    backgroundColor: _color.value
                  ),
                  child: new Container()
                )
              ),
              new Align(
                alignment: const FractionalOffset(0.0, 0.5),
                child: new Listener(
                  onPointerDown: _handlePointerDown,
                  child: new Align(
                    alignment: const FractionalOffset(1.0, 0.5),
                    widthFactor: _performance.progress,
                    child: new SizeObserver(
                      onSizeChanged: _handleSizeChanged,
                      child: new RepaintBoundary(
                        child: new Focus(
                          key: new GlobalObjectKey(config.key),
                          child: config.child
                        )
201
                      )
202 203
                    )
                  )
204
                )
205
              )
206 207
            ]
          )
208
        )
209 210
      );
    }
211
  }
212
}