bottom_sheet.dart 5.23 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
// 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/animation.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

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

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

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

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

38 39
  _BottomSheetState createState() => new _BottomSheetState();

40 41
  static AnimationController createAnimationController() {
    return new AnimationController(
42 43 44 45
      duration: _kBottomSheetDuration,
      debugLabel: 'BottomSheet'
    );
  }
46 47 48 49 50 51 52 53 54 55
}

class _BottomSheetState extends State<BottomSheet> {

  final _childKey = new GlobalKey(debugLabel: 'BottomSheet child');

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

57
  bool get _dismissUnderway => config.animationController.direction == AnimationDirection.reverse;
58 59 60 61

  void _handleDragUpdate(double delta) {
    if (_dismissUnderway)
      return;
62
    config.animationController.value -= delta / (_childHeight ?? delta);
63 64
  }

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

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

93
// PERSISTENT BOTTOM SHEETS
94

95
// See scaffold.dart
96 97


98
// MODAL BOTTOM SHEETS
99

100
class _ModalBottomSheetLayout extends OneChildLayoutDelegate {
101 102 103
  _ModalBottomSheetLayout(this.progress);

  final double progress;
104 105 106 107 108 109 110 111 112 113

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

114 115
  Offset getPositionForChild(Size size, Size childSize) {
    return new Offset(0.0, size.height - childSize.height * progress);
116 117 118 119
  }

  bool shouldRelayout(_ModalBottomSheetLayout oldDelegate) {
    return progress != oldDelegate.progress;
120 121 122
  }
}

123 124 125 126 127 128 129 130
class _ModalBottomSheet extends StatefulComponent {
  _ModalBottomSheet({ Key key, this.route }) : super(key: key);

  final _ModalBottomSheetRoute route;

  _ModalBottomSheetState createState() => new _ModalBottomSheetState();
}

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

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

  final WidgetBuilder builder;

Hixie's avatar
Hixie committed
162 163 164
  Duration get transitionDuration => _kBottomSheetDuration;
  bool get barrierDismissable => true;
  Color get barrierColor => Colors.black54;
165

166 167
  AnimationController createAnimationController() {
    return BottomSheet.createAnimationController();
168 169
  }

170
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> forwardAnimation) {
Hixie's avatar
Hixie committed
171
    return new _ModalBottomSheet(route: this);
172 173 174
  }
}

175 176 177
Future showModalBottomSheet({ BuildContext context, WidgetBuilder builder }) {
  assert(context != null);
  assert(builder != null);
178
  final Completer completer = new Completer();
Hixie's avatar
Hixie committed
179
  Navigator.push(context, new _ModalBottomSheetRoute(
180
    completer: completer,
181
    builder: builder
182 183 184
  ));
  return completer.future;
}