bottom_sheet.dart 5.23 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12
// 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.

import 'dart:async';

import 'package:flutter/widgets.dart';

import 'colors.dart';
import 'material.dart';

const Duration _kBottomSheetDuration = const Duration(milliseconds: 200);
13
const double _kMinFlingVelocity = 700.0;
14
const double _kCloseProgressThreshold = 0.5;
15 16
const Color _kTransparent = const Color(0x00000000);
const Color _kBarrierColor = Colors.black54;
17

18
class BottomSheet extends StatefulComponent {
19
  BottomSheet({
20
    Key key,
21
    this.animationController,
22 23 24 25 26
    this.onClosing,
    this.builder
  }) : super(key: key) {
    assert(onClosing != null);
  }
27

28 29
  /// The animation that controls the bottom sheet's position. The BottomSheet
  /// widget will manipulate the position of this animation, it is not just a
30
  /// passive observer.
31
  final AnimationController animationController;
32 33 34
  final VoidCallback onClosing;
  final WidgetBuilder builder;

35 36
  _BottomSheetState createState() => new _BottomSheetState();

37 38
  static AnimationController createAnimationController() {
    return new AnimationController(
39 40 41 42
      duration: _kBottomSheetDuration,
      debugLabel: 'BottomSheet'
    );
  }
43 44 45 46
}

class _BottomSheetState extends State<BottomSheet> {

47
  final GlobalKey _childKey = new GlobalKey(debugLabel: 'BottomSheet child');
48 49 50 51 52

  double get _childHeight {
    final RenderBox renderBox = _childKey.currentContext.findRenderObject();
    return renderBox.size.height;
  }
53

Adam Barth's avatar
Adam Barth committed
54
  bool get _dismissUnderway => config.animationController.status == AnimationStatus.reverse;
55 56 57 58

  void _handleDragUpdate(double delta) {
    if (_dismissUnderway)
      return;
59
    config.animationController.value -= delta / (_childHeight ?? delta);
60 61
  }

62
  void _handleDragEnd(Velocity velocity) {
63 64
    if (_dismissUnderway)
      return;
65 66
    if (velocity.pixelsPerSecond.dy > _kMinFlingVelocity) {
      double flingVelocity = -velocity.pixelsPerSecond.dy / _childHeight;
67
      config.animationController.fling(velocity: flingVelocity);
68
      if (flingVelocity < 0.0)
69
        config.onClosing();
70 71
    } else if (config.animationController.value < _kCloseProgressThreshold) {
      config.animationController.fling(velocity: -1.0);
72
      config.onClosing();
73
    } else {
74
      config.animationController.forward();
75
    }
76 77 78 79 80
  }

  Widget build(BuildContext context) {
    return new GestureDetector(
      onVerticalDragUpdate: _handleDragUpdate,
81
      onVerticalDragEnd: _handleDragEnd,
82
      child: new Material(
83
        key: _childKey,
84
        child: config.builder(context)
85
      )
86 87 88 89
    );
  }
}

90
// PERSISTENT BOTTOM SHEETS
91

92
// See scaffold.dart
93 94


95
// MODAL BOTTOM SHEETS
96

97
class _ModalBottomSheetLayout extends OneChildLayoutDelegate {
98 99 100
  _ModalBottomSheetLayout(this.progress);

  final double progress;
101 102 103 104 105 106 107 108 109 110

  BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
    return new BoxConstraints(
      minWidth: constraints.maxWidth,
      maxWidth: constraints.maxWidth,
      minHeight: 0.0,
      maxHeight: constraints.maxHeight * 9.0 / 16.0
    );
  }

111 112
  Offset getPositionForChild(Size size, Size childSize) {
    return new Offset(0.0, size.height - childSize.height * progress);
113 114 115 116
  }

  bool shouldRelayout(_ModalBottomSheetLayout oldDelegate) {
    return progress != oldDelegate.progress;
117 118 119
  }
}

120
class _ModalBottomSheet<T> extends StatefulComponent {
121 122
  _ModalBottomSheet({ Key key, this.route }) : super(key: key);

123
  final _ModalBottomSheetRoute<T> route;
124

125
  _ModalBottomSheetState<T> createState() => new _ModalBottomSheetState<T>();
126 127
}

128
class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
129
  Widget build(BuildContext context) {
130
    return new GestureDetector(
Hixie's avatar
Hixie committed
131
      onTap: () => Navigator.pop(context),
132
      child: new AnimatedBuilder(
133
        animation: config.route.animation,
134
        builder: (BuildContext context, Widget child) {
135 136
          return new ClipRect(
            child: new CustomOneChildLayout(
137
              delegate: new _ModalBottomSheetLayout(config.route.animation.value),
138
              child: new BottomSheet(
139
                animationController: config.route.animation,
Hixie's avatar
Hixie committed
140
                onClosing: () => Navigator.pop(context),
141
                builder: config.route.builder
142
              )
143
            )
144 145 146
          );
        }
      )
147 148 149 150
    );
  }
}

Hixie's avatar
Hixie committed
151 152 153 154 155
class _ModalBottomSheetRoute<T> extends PopupRoute<T> {
  _ModalBottomSheetRoute({
    Completer<T> completer,
    this.builder
  }) : super(completer: completer);
156 157 158

  final WidgetBuilder builder;

Hixie's avatar
Hixie committed
159 160 161
  Duration get transitionDuration => _kBottomSheetDuration;
  bool get barrierDismissable => true;
  Color get barrierColor => Colors.black54;
162

163 164
  AnimationController createAnimationController() {
    return BottomSheet.createAnimationController();
165 166
  }

167
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> forwardAnimation) {
168
    return new _ModalBottomSheet<T>(route: this);
169 170 171
  }
}

172
Future<dynamic/*=T*/> showModalBottomSheet/*<T>*/({ BuildContext context, WidgetBuilder builder }) {
173 174
  assert(context != null);
  assert(builder != null);
175 176
  final Completer<dynamic/*=T*/> completer = new Completer<dynamic/*=T*/>();
  Navigator.push(context, new _ModalBottomSheetRoute<dynamic/*=T*/>(
177
    completer: completer,
178
    builder: builder
179 180 181
  ));
  return completer.future;
}