drawer.dart 5.32 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 155
  final AnimatedColorValue _color = new AnimatedColorValue(Colors.transparent, end: Colors.black54);

156
  Widget build(BuildContext context) {
157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193
    HitTestBehavior behavior;
    Widget child;
    if (_performance.status == PerformanceStatus.dismissed) {
      behavior = HitTestBehavior.translucent;
      child = new Align(
        alignment: const FractionalOffset(0.0, 0.5),
        widthFactor: 1.0,
        child: new Container(width: _kEdgeDragWidth)
      );
    } else {
      _performance.updateVariable(_color);
      child = new RepaintBoundary(
        child: new Stack(<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
                    )
                  )
194
                )
195 196 197
              )
            )
          )
198 199 200 201 202 203 204 205
        ])
      );
    }
    return new GestureDetector(
      onHorizontalDragUpdate: _move,
      onHorizontalDragEnd: _settle,
      behavior: behavior,
      child: child
206 207
    );
  }
208
}