提交 a4147ee2 编写于 作者: H Hans Muller

Adds initial versions of DropdownButton DropdownMenuItem

Adds a dropdown menu button as shown in the Material design spec here: https://www.google.com/design/spec/components/buttons.html#buttons-dropdown-buttons. It's the video at the bottom of this section of the Material design spec.

This version of the component doesn't deal with scrollable menus or
constrain the height of the menu.
上级 5ee6a6e5
// 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 'package:flutter/material.dart';
class DropdownDemo extends StatefulComponent {
DropdownDemo();
DropdownDemoState createState() => new DropdownDemoState();
}
class DropdownDemoState extends State<DropdownDemo> {
dynamic _value = 0;
List <DropdownMenuItem> _buildItems() {
return ["One", "Two", "Free", "Four"].map((String label) {
return new DropdownMenuItem(value: label, child: new Text(label));
})
.toList();
}
Widget build(BuildContext context) {
Widget dropdown = new DropdownButton(
items: _buildItems(),
value: _value,
onChanged: (dynamic newValue) {
setState(() {
if (newValue != null)
_value = newValue;
});
}
);
return new Scaffold(
toolBar: new ToolBar(center: new Text('DropdownDemo Demo')),
body: new Container(
decoration: new BoxDecoration(backgroundColor: Theme.of(context).primarySwatch[50]),
child: new Center(child: dropdown)
)
);
}
}
void main() {
runApp(new MaterialApp(
title: 'DropdownDemo',
theme: new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.blue,
accentColor: Colors.redAccent[200]
),
routes: {
'/': (RouteArguments args) => new DropdownDemo(),
}
));
}
......@@ -18,6 +18,7 @@ export 'src/material/drawer.dart';
export 'src/material/drawer_divider.dart';
export 'src/material/drawer_header.dart';
export 'src/material/drawer_item.dart';
export 'src/material/dropdown.dart';
export 'src/material/edges.dart';
export 'src/material/flat_button.dart';
export 'src/material/floating_action_button.dart';
......
// 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 'dart:ui' as ui;
import 'package:flutter/animation.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'icon.dart';
import 'ink_well.dart';
import 'shadows.dart';
import 'theme.dart';
const Duration _kMenuDuration = const Duration(milliseconds: 300);
const double _kMenuItemHeight = 48.0;
const double _kMenuHorizontalPadding = 36.0;
const double _kBaselineOffsetFromBottom = 20.0;
const Border _kDropdownUnderline = const Border(bottom: const BorderSide(color: const Color(0xFFBDBDBD), width: 2.0));
class _DropdownMenu extends StatelessComponent {
_DropdownMenu({
Key key,
this.items,
this.rect,
this.performance,
this.selectedIndex,
this.level: 4
}) : super(key: key) {
assert(items != null);
assert(performance != null);
}
final List<DropdownMenuItem> items;
final Rect rect;
final PerformanceView performance;
final int selectedIndex;
final int level;
Widget build(BuildContext context) {
// The menu is shown in three stages (unit timing in brackets):
// [0 - 0.25] - Fade in a rect-sized menu container with the selected item.
// [0.25 - 0.5] - Grow the otherwise empty menu container from the center
// until it's big enough for as many items as we're going to show.
// [0.5 - 1.0] Fade in the remaining visible items from top to bottom.
//
// When the menu is dismissed we just fade the entire thing out
// in the first 0.25.
final double unit = 0.5 / (items.length + 1.5);
final List<Widget> children = <Widget>[];
for (int itemIndex = 0; itemIndex < items.length; ++itemIndex) {
AnimatedValue<double> opacity;
if (itemIndex == selectedIndex) {
opacity = new AnimatedValue<double>(0.0, end: 1.0, curve: const Interval(0.0, 0.001), reverseCurve: const Interval(0.75, 1.0));
} else {
final double start = (0.5 + (itemIndex + 1) * unit).clamp(0.0, 1.0);
final double end = (start + 1.5 * unit).clamp(0.0, 1.0);
opacity = new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(start, end), reverseCurve: const Interval(0.75, 1.0));
}
children.add(new FadeTransition(
performance: performance,
opacity: opacity,
child: new InkWell(
child: items[itemIndex],
onTap: () {
Navigator.of(context).pop(items[itemIndex].value);
}
)
));
}
final AnimatedValue<double> menuOpacity = new AnimatedValue<double>(0.0,
end: 1.0,
curve: new Interval(0.0, 0.25),
reverseCurve: new Interval(0.75, 1.0)
);
final AnimatedValue<double> menuTop = new AnimatedValue<double>(rect.top,
end: rect.top - selectedIndex * rect.height,
curve: new Interval(0.25, 0.5),
reverseCurve: const Interval(0.0, 0.001)
);
final AnimatedValue<double> menuBottom = new AnimatedValue<double>(rect.bottom,
end: menuTop.end + items.length * rect.height,
curve: new Interval(0.25, 0.5),
reverseCurve: const Interval(0.0, 0.001)
);
final BoxPainter menuPainter = new BoxPainter(new BoxDecoration(
backgroundColor: Theme.of(context).canvasColor,
borderRadius: 2.0,
boxShadow: shadows[level]
));
return new FadeTransition(
performance: performance,
opacity: menuOpacity,
child: new BuilderTransition(
performance: performance,
variables: <AnimatedValue<double>>[menuTop, menuBottom],
builder: (BuildContext context) {
RenderBox renderBox = context.findRenderObject();
return new CustomPaint(
child: new ScrollableViewport(child: new Container(child: new Column(children))),
onPaint: (ui.Canvas canvas, Size size) {
double top = renderBox.globalToLocal(new Point(0.0, menuTop.value)).y;
double bottom = renderBox.globalToLocal(new Point(0.0, menuBottom.value)).y;
menuPainter.paint(canvas, new Rect.fromLTRB(0.0, top, size.width, bottom));
}
);
}
)
);
}
}
class _MenuRoute extends PerformanceRoute {
_MenuRoute({
this.completer,
this.items,
this.selectedIndex,
this.rect,
this.level: 4
});
final Completer completer;
final Rect rect;
final List<DropdownMenuItem> items;
final int level;
final int selectedIndex;
bool get ephemeral => true;
bool get modal => true;
bool get opaque => false;
Duration get transitionDuration => _kMenuDuration;
Widget build(RouteArguments args) {
final Size navigatorSize = navigator.context.findRenderObject().size;
final RelativeRect menuRect = new RelativeRect.fromSize(rect, navigatorSize);
return new Positioned(
top: menuRect.top - (selectedIndex * rect.height),
right: menuRect.right - _kMenuHorizontalPadding,
left: menuRect.left - _kMenuHorizontalPadding,
child: new Focus(
key: new GlobalObjectKey(this),
autofocus: true,
child: new _DropdownMenu(
items: items,
selectedIndex: selectedIndex,
rect: rect,
level: level,
performance: performance
)
)
);
}
void didPop([dynamic result]) {
completer.complete(result);
super.didPop(result);
}
}
class DropdownMenuItem extends StatelessComponent {
DropdownMenuItem({
Key key,
this.value,
this.child
}) : super(key: key);
final Widget child;
final dynamic value;
Widget build(BuildContext context) {
return new Container(
height: _kMenuItemHeight,
padding: const EdgeDims.only(left: 8.0, right: 8.0, top: 6.0),
child: new DefaultTextStyle(
style: Theme.of(context).text.subhead,
child: new Baseline(
baseline: _kMenuItemHeight - _kBaselineOffsetFromBottom,
child: child
)
)
);
}
}
class DropdownButton extends StatelessComponent {
DropdownButton({
Key key,
this.items,
this.value,
this.onChanged,
this.level: 4
}) : super(key: key);
final List<DropdownMenuItem> items;
final dynamic value;
final ValueChanged onChanged;
final int level;
void _showDropdown(BuildContext context, int selectedIndex, GlobalKey indexedStackKey) {
final RenderBox renderBox = indexedStackKey.currentContext.findRenderObject();
final Rect rect = renderBox.localToGlobal(Point.origin) & renderBox.size;
final Completer completer = new Completer();
Navigator.of(context).push(new _MenuRoute(
completer: completer,
items: items,
selectedIndex: selectedIndex,
rect: rect,
level: level
));
completer.future.then((dynamic newValue) {
if (onChanged != null)
onChanged(newValue);
});
}
Widget build(BuildContext context) {
GlobalKey indexedStackKey = new GlobalKey(label: 'DropdownButton.IndexedStack');
int selectedIndex = 0;
for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
if (items[itemIndex].value == value) {
selectedIndex = itemIndex;
break;
}
}
return new GestureDetector(
child: new Container(
decoration: new BoxDecoration(border: _kDropdownUnderline),
child: new IntrinsicWidth(
child: new Row(<Widget>[
new IndexedStack(items,
key: indexedStackKey,
index: selectedIndex,
horizontalAlignment: 0.5
),
new Container(
child: new Icon(type: 'navigation/arrow_drop_down', size: 36),
padding: const EdgeDims.only(top: 6.0)
)
])
)
),
onTap: () {
_showDropdown(context, selectedIndex, indexedStackKey);
}
);
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册