popup_menu.dart 4.6 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
import 'dart:async';
6

7 8
import 'package:flutter/animation.dart';
import 'package:flutter/widgets.dart';
9 10

import 'ink_well.dart';
11
import 'material.dart';
12
import 'theme.dart';
13 14

const Duration _kMenuDuration = const Duration(milliseconds: 300);
15
const double _kBaselineOffsetFromBottom = 20.0;
16
const double _kMenuCloseIntervalEnd = 2.0 / 3.0;
17
const double _kMenuHorizontalPadding = 16.0;
18 19 20
const double _kMenuItemHeight = 48.0;
const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep;
const double _kMenuMinWidth = 2.0 * _kMenuWidthStep;
21
const double _kMenuVerticalPadding = 8.0;
22 23 24 25 26 27 28 29 30 31 32 33 34
const double _kMenuWidthStep = 56.0;

class PopupMenuItem<T> extends StatelessComponent {
  PopupMenuItem({
    Key key,
    this.value,
    this.child
  }) : super(key: key);

  final Widget child;
  final T value;

  Widget build(BuildContext context) {
Hixie's avatar
Hixie committed
35 36 37 38 39 40 41 42 43 44
    return new MergeSemantics(
      child: new Container(
        height: _kMenuItemHeight,
        padding: const EdgeDims.symmetric(horizontal: _kMenuHorizontalPadding),
        child: new DefaultTextStyle(
          style: Theme.of(context).text.subhead,
          child: new Baseline(
            baseline: _kMenuItemHeight - _kBaselineOffsetFromBottom,
            child: child
          )
45 46 47 48 49
        )
      )
    );
  }
}
50

Hixie's avatar
Hixie committed
51
class _PopupMenu<T> extends StatelessComponent {
Adam Barth's avatar
Adam Barth committed
52
  _PopupMenu({
53
    Key key,
Adam Barth's avatar
Adam Barth committed
54 55
    this.route
  }) : super(key: key);
56

Hixie's avatar
Hixie committed
57
  final _PopupMenuRoute<T> route;
58

59
  Widget build(BuildContext context) {
Adam Barth's avatar
Adam Barth committed
60
    double unit = 1.0 / (route.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade.
Hixie's avatar
Hixie committed
61
    List<Widget> children = <Widget>[];
62

Adam Barth's avatar
Adam Barth committed
63
    for (int i = 0; i < route.items.length; ++i) {
64 65
      double start = (i + 1) * unit;
      double end = (start + 1.5 * unit).clamp(0.0, 1.0);
66 67 68 69 70 71
      CurvedAnimation opacity = new CurvedAnimation(
        parent: route.animation,
        curve: new Interval(start, end)
      );
      children.add(new FadeTransition(
        opacity: opacity,
72
        child: new InkWell(
Hixie's avatar
Hixie committed
73
          onTap: () => Navigator.pop(context, route.items[i].value),
Adam Barth's avatar
Adam Barth committed
74
          child: route.items[i]
75
        ))
76
      );
77
    }
78

79 80 81 82 83 84 85 86 87 88 89 90
    final CurveTween opacity = new CurveTween(curve: new Interval(0.0, 1.0 / 3.0));
    final CurveTween width = new CurveTween(curve: new Interval(0.0, unit));
    final CurveTween height = new CurveTween(curve: new Interval(0.0, unit * route.items.length));

    Widget child = new ConstrainedBox(
      constraints: new BoxConstraints(
        minWidth: _kMenuMinWidth,
        maxWidth: _kMenuMaxWidth
      ),
      child: new IntrinsicWidth(
        stepWidth: _kMenuWidthStep,
        child: new Block(
91
          children: children,
92 93 94 95 96 97
          padding: const EdgeDims.symmetric(
            vertical: _kMenuVerticalPadding
          )
        )
      )
    );
Adam Barth's avatar
Adam Barth committed
98

99 100
    return new AnimatedBuilder(
      animation: route.animation,
101
      builder: (BuildContext context, Widget child) {
102
        return new Opacity(
103
          opacity: opacity.evaluate(route.animation),
104 105 106 107 108
          child: new Material(
            type: MaterialType.card,
            elevation: route.elevation,
            child: new Align(
              alignment: const FractionalOffset(1.0, 0.0),
109 110 111
              widthFactor: width.evaluate(route.animation),
              heightFactor: height.evaluate(route.animation),
              child: child
112
            )
Adam Barth's avatar
Adam Barth committed
113
          )
114
        );
115 116
      },
      child: child
117 118 119
    );
  }
}
120

Hixie's avatar
Hixie committed
121 122 123 124 125 126 127
class _PopupMenuRoute<T> extends PopupRoute<T> {
  _PopupMenuRoute({
    Completer<T> completer,
    this.position,
    this.items,
    this.elevation
  }) : super(completer: completer);
128

129
  final ModalPosition position;
Hixie's avatar
Hixie committed
130
  final List<PopupMenuItem<T>> items;
Hans Muller's avatar
Hans Muller committed
131
  final int elevation;
132

133 134 135 136
  ModalPosition getPosition(BuildContext context) {
    return position;
  }

137
  Animation<double> createAnimation() {
138 139
    return new CurvedAnimation(
      parent: super.createAnimation(),
140
      reverseCurve: new Interval(0.0, _kMenuCloseIntervalEnd)
141
    );
142 143
  }

144
  Duration get transitionDuration => _kMenuDuration;
Hixie's avatar
Hixie committed
145 146
  bool get barrierDismissable => true;
  Color get barrierColor => null;
147

148
  Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> forwardAnimation) {
149 150
    return new _PopupMenu(route: this);
  }
151 152
}

Hans Muller's avatar
Hans Muller committed
153
Future showMenu({ BuildContext context, ModalPosition position, List<PopupMenuItem> items, int elevation: 8 }) {
154
  Completer completer = new Completer();
Hixie's avatar
Hixie committed
155
  Navigator.push(context, new _PopupMenuRoute(
156 157
    completer: completer,
    position: position,
Adam Barth's avatar
Adam Barth committed
158
    items: items,
Hans Muller's avatar
Hans Muller committed
159
    elevation: elevation
160 161 162
  ));
  return completer.future;
}