Gallery Menu Demo etc
This commit is contained in:
parent
0c05666ee1
commit
f1df2bd7c0
@ -27,6 +27,8 @@ material-design-icons:
|
||||
- name: action/account_circle
|
||||
- name: action/alarm
|
||||
- name: action/android
|
||||
- name: action/delete
|
||||
- name: action/done
|
||||
- name: action/event
|
||||
- name: action/face
|
||||
- name: action/home
|
||||
@ -58,3 +60,4 @@ material-design-icons:
|
||||
- name: navigation/menu
|
||||
- name: navigation/more_horiz
|
||||
- name: navigation/more_vert
|
||||
- name: social/person_add
|
||||
|
@ -129,6 +129,8 @@ class GridListDemoGridDelegate extends FixedColumnCountGridDelegate {
|
||||
}
|
||||
|
||||
class GridListDemo extends StatefulComponent {
|
||||
GridListDemo({ Key key }) : super(key: key);
|
||||
|
||||
GridListDemoState createState() => new GridListDemoState();
|
||||
}
|
||||
|
||||
|
@ -175,11 +175,9 @@ class ListDemoState extends State<ListDemo> {
|
||||
)
|
||||
]
|
||||
),
|
||||
body: new Padding(
|
||||
padding: const EdgeDims.all(8.0),
|
||||
child: new Block(
|
||||
children: items.map((String item) => buildListItem(context, item)).toList()
|
||||
)
|
||||
body: new Block(
|
||||
padding: new EdgeDims.all(_isDense ? 4.0 : 8.0),
|
||||
children: items.map((String item) => buildListItem(context, item)).toList()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
214
examples/material_gallery/lib/demo/menu_demo.dart
Normal file
214
examples/material_gallery/lib/demo/menu_demo.dart
Normal file
@ -0,0 +1,214 @@
|
||||
// Copyright 2016 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 MenuDemo extends StatefulComponent {
|
||||
MenuDemo({ Key key }) : super(key: key);
|
||||
|
||||
MenuDemoState createState() => new MenuDemoState();
|
||||
}
|
||||
|
||||
class MenuDemoState extends State<MenuDemo> {
|
||||
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
|
||||
|
||||
final String _simpleValue1 = 'Menu item value one';
|
||||
final String _simpleValue2 = 'Menu item value two';
|
||||
final String _simpleValue3 = 'Menu item value three';
|
||||
String _simpleValue;
|
||||
|
||||
final String _checkedValue1 = 'One';
|
||||
final String _checkedValue2 = 'Two';
|
||||
final String _checkedValue3 = 'Free';
|
||||
final String _checkedValue4 = 'Four';
|
||||
List<String> _checkedValues;
|
||||
|
||||
void initState() {
|
||||
super.initState();
|
||||
_simpleValue = _simpleValue2;
|
||||
_checkedValues = <String>[_checkedValue3];
|
||||
}
|
||||
|
||||
void showInSnackBar(String value) {
|
||||
_scaffoldKey.currentState.showSnackBar(new SnackBar(
|
||||
content: new Text(value)
|
||||
));
|
||||
}
|
||||
|
||||
void showMenuSelection(String value) {
|
||||
if (<String>[_simpleValue1, _simpleValue2, _simpleValue3].contains(value))
|
||||
_simpleValue = value;
|
||||
showInSnackBar('You selected: $value');
|
||||
}
|
||||
|
||||
void showCheckedMenuSelections(String value) {
|
||||
if (_checkedValues.contains(value))
|
||||
_checkedValues.remove(value);
|
||||
else
|
||||
_checkedValues.add(value);
|
||||
|
||||
showInSnackBar('Checked $_checkedValues');
|
||||
}
|
||||
|
||||
bool isChecked(String value) => _checkedValues.contains(value);
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new Scaffold(
|
||||
key: _scaffoldKey,
|
||||
toolBar: new ToolBar(
|
||||
center: new Text('Menus'),
|
||||
right: <Widget>[
|
||||
new PopupMenuButton<String>(
|
||||
onSelected: showMenuSelection,
|
||||
items: <PopupMenuItem>[
|
||||
new PopupMenuItem(
|
||||
value: 'ToolBar Menu',
|
||||
child: new Text('ToolBar Menu')
|
||||
),
|
||||
new PopupMenuItem(
|
||||
value: 'Right Here',
|
||||
child: new Text('Right Here')
|
||||
),
|
||||
new PopupMenuItem(
|
||||
value: 'Hooray!',
|
||||
child: new Text('Hooray!')
|
||||
),
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
body: new Block(
|
||||
padding: const EdgeDims.all(8.0),
|
||||
children: <Widget>[
|
||||
// Pressing the PopupMenuButton on the right of this item shows
|
||||
// a simple menu with one disabled item. Typically the contents
|
||||
// of this "contextual menu" would reflect the app's state.
|
||||
new ListItem(
|
||||
primary: new Text('An item with a context menu button'),
|
||||
right: new PopupMenuButton<String>(
|
||||
onSelected: showMenuSelection,
|
||||
items: <PopupMenuItem>[
|
||||
new PopupMenuItem(
|
||||
value: _simpleValue1,
|
||||
child: new Text('Context menu item one')
|
||||
),
|
||||
new PopupMenuItem(
|
||||
disabled: true,
|
||||
child: new Text('A disabled menu item')
|
||||
),
|
||||
new PopupMenuItem(
|
||||
value: _simpleValue3,
|
||||
child: new Text('Context menu item three')
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
// Pressing the PopupMenuButton on the right of this item shows
|
||||
// a menu whose items have text labels and icons and a divider
|
||||
// That separates the first three items from the last one.
|
||||
new ListItem(
|
||||
primary: new Text('An item with a sectioned menu'),
|
||||
right: new PopupMenuButton<String>(
|
||||
onSelected: showMenuSelection,
|
||||
items: <PopupMenuItem>[
|
||||
new PopupMenuItem(
|
||||
value: 'Preview',
|
||||
child: new ListItem(
|
||||
left: new Icon(icon: 'action/visibility'),
|
||||
primary: new Text('Preview')
|
||||
)
|
||||
),
|
||||
new PopupMenuItem(
|
||||
value: 'Share',
|
||||
child: new ListItem(
|
||||
left: new Icon(icon: 'social/person_add'),
|
||||
primary: new Text('Share')
|
||||
)
|
||||
),
|
||||
new PopupMenuItem(
|
||||
value: 'Get Link',
|
||||
hasDivider: true,
|
||||
child: new ListItem(
|
||||
left: new Icon(icon: 'content/link'),
|
||||
primary: new Text('Get Link')
|
||||
)
|
||||
),
|
||||
new PopupMenuItem(
|
||||
value: 'Remove',
|
||||
child: new ListItem(
|
||||
left: new Icon(icon: 'action/delete'),
|
||||
primary: new Text('Remove')
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
),
|
||||
// This entire list item is a PopupMenuButton. Tapping anywhere shows
|
||||
// a menu whose current value is highlighted and aligned over the
|
||||
// list item's center line.
|
||||
new PopupMenuButton<String>(
|
||||
initialValue: _simpleValue,
|
||||
onSelected: showMenuSelection,
|
||||
child: new ListItem(
|
||||
primary: new Text('An item with a simple menu'),
|
||||
secondary: new Text(_simpleValue)
|
||||
),
|
||||
items: <PopupMenuItem>[
|
||||
new PopupMenuItem(
|
||||
value: _simpleValue1,
|
||||
child: new Text(_simpleValue1)
|
||||
),
|
||||
new PopupMenuItem(
|
||||
value: _simpleValue2,
|
||||
child: new Text(_simpleValue2)
|
||||
),
|
||||
new PopupMenuItem(
|
||||
value: _simpleValue3,
|
||||
child: new Text(_simpleValue3)
|
||||
)
|
||||
]
|
||||
),
|
||||
// Pressing the PopupMenuButton on the right of this item shows a menu
|
||||
// whose items have checked icons that reflect this app's state.
|
||||
new ListItem(
|
||||
primary: new Text('An item with a checklist menu'),
|
||||
right: new PopupMenuButton<String>(
|
||||
onSelected: showCheckedMenuSelections,
|
||||
items: <PopupMenuItem>[
|
||||
new PopupMenuItem(
|
||||
value: _checkedValue1,
|
||||
child: new ListItem(
|
||||
left: new Icon(icon: isChecked(_checkedValue1) ? 'action/done' : null),
|
||||
primary: new Text(_checkedValue1)
|
||||
)
|
||||
),
|
||||
new PopupMenuItem(
|
||||
value: _checkedValue2,
|
||||
child: new ListItem(
|
||||
left: new Icon(icon: isChecked(_checkedValue2) ? 'action/done' : null),
|
||||
primary: new Text(_checkedValue2)
|
||||
)
|
||||
),
|
||||
new PopupMenuItem(
|
||||
value: _checkedValue3,
|
||||
child: new ListItem(
|
||||
left: new Icon(icon: isChecked(_checkedValue3) ? 'action/done' : null),
|
||||
primary: new Text(_checkedValue3)
|
||||
)
|
||||
),
|
||||
new PopupMenuItem(
|
||||
value: _checkedValue4,
|
||||
child: new ListItem(
|
||||
left: new Icon(icon: isChecked(_checkedValue4) ? 'action/done' : null),
|
||||
primary: new Text(_checkedValue4)
|
||||
)
|
||||
),
|
||||
]
|
||||
)
|
||||
)
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ import '../demo/grid_list_demo.dart';
|
||||
import '../demo/icons_demo.dart';
|
||||
import '../demo/list_demo.dart';
|
||||
import '../demo/modal_bottom_sheet_demo.dart';
|
||||
import '../demo/menu_demo.dart';
|
||||
import '../demo/page_selector_demo.dart';
|
||||
import '../demo/persistent_bottom_sheet_demo.dart';
|
||||
import '../demo/progress_indicator_demo.dart';
|
||||
@ -108,6 +109,7 @@ class GalleryHomeState extends State<GalleryHome> {
|
||||
new GalleryDemo(title: 'Icons', builder: () => new IconsDemo()),
|
||||
new GalleryDemo(title: 'List', builder: () => new ListDemo()),
|
||||
new GalleryDemo(title: 'Modal Bottom Sheet', builder: () => new ModalBottomSheetDemo()),
|
||||
new GalleryDemo(title: 'Menus', builder: () => new MenuDemo()),
|
||||
new GalleryDemo(title: 'Page Selector', builder: () => new PageSelectorDemo()),
|
||||
new GalleryDemo(title: 'Persistent Bottom Sheet', builder: () => new PersistentBottomSheetDemo()),
|
||||
new GalleryDemo(title: 'Progress Indicators', builder: () => new ProgressIndicatorDemo()),
|
||||
|
@ -27,12 +27,11 @@ class Icon extends StatelessComponent {
|
||||
Icon({
|
||||
Key key,
|
||||
this.size: IconSize.s24,
|
||||
this.icon: '',
|
||||
this.icon,
|
||||
this.colorTheme,
|
||||
this.color
|
||||
}) : super(key: key) {
|
||||
assert(size != null);
|
||||
assert(icon != null);
|
||||
}
|
||||
|
||||
final IconSize size;
|
||||
@ -54,6 +53,14 @@ class Icon extends StatelessComponent {
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
final int iconSize = _kIconSize[size];
|
||||
if (icon == null) {
|
||||
return new SizedBox(
|
||||
width: iconSize.toDouble(),
|
||||
height: iconSize.toDouble()
|
||||
);
|
||||
}
|
||||
|
||||
String category = '';
|
||||
String subtype = '';
|
||||
List<String> parts = icon.split('/');
|
||||
@ -62,7 +69,6 @@ class Icon extends StatelessComponent {
|
||||
subtype = parts[1];
|
||||
}
|
||||
final IconThemeColor iconThemeColor = _getIconThemeColor(context);
|
||||
final int iconSize = _kIconSize[size];
|
||||
|
||||
String colorSuffix;
|
||||
switch(iconThemeColor) {
|
||||
|
@ -6,6 +6,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import 'icon_button.dart';
|
||||
import 'ink_well.dart';
|
||||
import 'material.dart';
|
||||
import 'theme.dart';
|
||||
@ -19,24 +20,37 @@ const double _kMenuMaxWidth = 5.0 * _kMenuWidthStep;
|
||||
const double _kMenuMinWidth = 2.0 * _kMenuWidthStep;
|
||||
const double _kMenuVerticalPadding = 8.0;
|
||||
const double _kMenuWidthStep = 56.0;
|
||||
const double _kMenuScreenPadding = 8.0;
|
||||
|
||||
class PopupMenuItem<T> extends StatelessComponent {
|
||||
PopupMenuItem({
|
||||
Key key,
|
||||
this.value,
|
||||
this.disabled: false,
|
||||
this.hasDivider: false,
|
||||
this.child
|
||||
}) : super(key: key);
|
||||
|
||||
final Widget child;
|
||||
final T value;
|
||||
final bool disabled;
|
||||
final bool hasDivider;
|
||||
final Widget child;
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
final ThemeData theme = Theme.of(context);
|
||||
TextStyle style = theme.text.subhead;
|
||||
if (disabled)
|
||||
style = style.copyWith(color: theme.disabledColor);
|
||||
|
||||
return new MergeSemantics(
|
||||
child: new Container(
|
||||
height: _kMenuItemHeight,
|
||||
padding: const EdgeDims.symmetric(horizontal: _kMenuHorizontalPadding),
|
||||
decoration: !hasDivider ? null : new BoxDecoration(
|
||||
border: new Border(bottom: new BorderSide(color: theme.dividerColor))
|
||||
),
|
||||
child: new DefaultTextStyle(
|
||||
style: Theme.of(context).text.subhead,
|
||||
style: style,
|
||||
child: new Baseline(
|
||||
baseline: _kMenuItemHeight - _kBaselineOffsetFromBottom,
|
||||
child: child
|
||||
@ -60,19 +74,27 @@ class _PopupMenu<T> extends StatelessComponent {
|
||||
List<Widget> children = <Widget>[];
|
||||
|
||||
for (int i = 0; i < route.items.length; ++i) {
|
||||
double start = (i + 1) * unit;
|
||||
double end = (start + 1.5 * unit).clamp(0.0, 1.0);
|
||||
final double start = (i + 1) * unit;
|
||||
final double end = (start + 1.5 * unit).clamp(0.0, 1.0);
|
||||
CurvedAnimation opacity = new CurvedAnimation(
|
||||
parent: route.animation,
|
||||
curve: new Interval(start, end)
|
||||
);
|
||||
final bool disabled = route.items[i].disabled;
|
||||
Widget item = route.items[i];
|
||||
if (route.initialValue != null && route.initialValue == route.items[i].value) {
|
||||
item = new Container(
|
||||
decoration: new BoxDecoration(backgroundColor: Theme.of(context).highlightColor),
|
||||
child: item
|
||||
);
|
||||
}
|
||||
children.add(new FadeTransition(
|
||||
opacity: opacity,
|
||||
child: new InkWell(
|
||||
onTap: () => Navigator.pop(context, route.items[i].value),
|
||||
child: route.items[i]
|
||||
))
|
||||
);
|
||||
onTap: disabled ? null : () { Navigator.pop(context, route.items[i].value); },
|
||||
child: item
|
||||
)
|
||||
));
|
||||
}
|
||||
|
||||
final CurveTween opacity = new CurveTween(curve: new Interval(0.0, 1.0 / 3.0));
|
||||
@ -117,21 +139,64 @@ class _PopupMenu<T> extends StatelessComponent {
|
||||
}
|
||||
}
|
||||
|
||||
class _PopupMenuRouteLayout extends OneChildLayoutDelegate {
|
||||
_PopupMenuRouteLayout(this.position, this.selectedIndex);
|
||||
|
||||
final ModalPosition position;
|
||||
final int selectedIndex;
|
||||
|
||||
BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
|
||||
return new BoxConstraints(
|
||||
minWidth: 0.0,
|
||||
maxWidth: constraints.maxWidth,
|
||||
minHeight: 0.0,
|
||||
maxHeight: constraints.maxHeight
|
||||
);
|
||||
}
|
||||
|
||||
// Put the child wherever position specifies, so long as it will fit within the
|
||||
// specified parent size padded (inset) by 8. If necessary, adjust the child's
|
||||
// position so that it fits.
|
||||
Offset getPositionForChild(Size size, Size childSize) {
|
||||
double x = position?.left
|
||||
?? (position?.right != null ? size.width - (position.right + childSize.width) : _kMenuScreenPadding);
|
||||
double y = position?.top
|
||||
?? (position?.bottom != null ? size.height - (position.bottom - childSize.height) : _kMenuScreenPadding);
|
||||
|
||||
if (selectedIndex != -1)
|
||||
y -= (_kMenuItemHeight * selectedIndex) + _kMenuVerticalPadding + _kMenuItemHeight / 2.0;
|
||||
|
||||
if (x < _kMenuScreenPadding)
|
||||
x = _kMenuScreenPadding;
|
||||
else if (x + childSize.width > size.width - 2 * _kMenuScreenPadding)
|
||||
x = size.width - childSize.width - _kMenuScreenPadding;
|
||||
if (y < _kMenuScreenPadding)
|
||||
y = _kMenuScreenPadding;
|
||||
else if (y + childSize.height > size.height - 2 * _kMenuScreenPadding)
|
||||
y = size.height - childSize.height - _kMenuScreenPadding;
|
||||
return new Offset(x, y);
|
||||
}
|
||||
|
||||
bool shouldRelayout(_PopupMenuRouteLayout oldDelegate) {
|
||||
return position != oldDelegate.position;
|
||||
}
|
||||
}
|
||||
|
||||
class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
_PopupMenuRoute({
|
||||
Completer<T> completer,
|
||||
this.position,
|
||||
this.items,
|
||||
this.initialValue,
|
||||
this.elevation
|
||||
}) : super(completer: completer);
|
||||
|
||||
final ModalPosition position;
|
||||
final List<PopupMenuItem<T>> items;
|
||||
final dynamic initialValue;
|
||||
final int elevation;
|
||||
|
||||
ModalPosition getPosition(BuildContext context) {
|
||||
return position;
|
||||
}
|
||||
ModalPosition getPosition(BuildContext context) => null;
|
||||
|
||||
Animation<double> createAnimation() {
|
||||
return new CurvedAnimation(
|
||||
@ -145,17 +210,110 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
|
||||
Color get barrierColor => null;
|
||||
|
||||
Widget buildPage(BuildContext context, Animation<double> animation, Animation<double> forwardAnimation) {
|
||||
return new _PopupMenu(route: this);
|
||||
int selectedIndex = -1;
|
||||
if (initialValue != null) {
|
||||
for (int i = 0; i < items.length; i++)
|
||||
if (initialValue == items[i].value) {
|
||||
selectedIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
final Size screenSize = MediaQuery.of(context).size;
|
||||
return new ConstrainedBox(
|
||||
constraints: new BoxConstraints(maxWidth: screenSize.width, maxHeight: screenSize.height),
|
||||
child: new CustomOneChildLayout(
|
||||
delegate: new _PopupMenuRouteLayout(position, selectedIndex),
|
||||
child: new _PopupMenu(route: this)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Future showMenu({ BuildContext context, ModalPosition position, List<PopupMenuItem> items, int elevation: 8 }) {
|
||||
Completer completer = new Completer();
|
||||
Navigator.push(context, new _PopupMenuRoute(
|
||||
/// Show a popup menu that contains the [items] at [position]. If [initialValue]
|
||||
/// is specified then the first item with a matching value will be highlighted
|
||||
/// and the value of [position] implies where the left, center point of the
|
||||
/// highlighted item should appear. If [initialValue] is not specified then position
|
||||
/// implies the menu's origin.
|
||||
Future/*<T>*/ showMenu/*<T>*/({
|
||||
BuildContext context,
|
||||
ModalPosition position,
|
||||
List<PopupMenuItem/*<T>*/> items,
|
||||
dynamic/*=T*/ initialValue,
|
||||
int elevation: 8
|
||||
}) {
|
||||
assert(context != null);
|
||||
assert(items != null && items.length > 0);
|
||||
Completer completer = new Completer/*<T>*/();
|
||||
Navigator.push(context, new _PopupMenuRoute/*<T>*/(
|
||||
completer: completer,
|
||||
position: position,
|
||||
items: items,
|
||||
initialValue: initialValue,
|
||||
elevation: elevation
|
||||
));
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
/// A callback that is passed the value of the PopupMenuItem that caused
|
||||
/// its menu to be dismissed.
|
||||
typedef void PopupMenuItemSelected<T>(T value);
|
||||
|
||||
/// Displays a menu when pressed and calls [onSelected] when the menu is dismissed
|
||||
/// because an item was selected. The value passed to [onSelected] is the value of
|
||||
/// the selected menu item. If child is null then a standard 'navigation/more_vert'
|
||||
/// icon is created.
|
||||
class PopupMenuButton<T> extends StatefulComponent {
|
||||
PopupMenuButton({
|
||||
Key key,
|
||||
this.items,
|
||||
this.initialValue,
|
||||
this.onSelected,
|
||||
this.tooltip: 'Show menu',
|
||||
this.elevation: 8,
|
||||
this.child
|
||||
}) : super(key: key);
|
||||
|
||||
final List<PopupMenuItem<T>> items;
|
||||
final T initialValue;
|
||||
final PopupMenuItemSelected<T> onSelected;
|
||||
final String tooltip;
|
||||
final int elevation;
|
||||
final Widget child;
|
||||
|
||||
_PopupMenuButtonState<T> createState() => new _PopupMenuButtonState<T>();
|
||||
}
|
||||
|
||||
class _PopupMenuButtonState<T> extends State<PopupMenuButton<T>> {
|
||||
void showButtonMenu(BuildContext context) {
|
||||
final RenderBox renderBox = context.findRenderObject();
|
||||
final Point topLeft = renderBox.localToGlobal(Point.origin);
|
||||
showMenu/*<T>*/(
|
||||
context: context,
|
||||
elevation: config.elevation,
|
||||
items: config.items,
|
||||
initialValue: config.initialValue,
|
||||
position: new ModalPosition(
|
||||
left: topLeft.x,
|
||||
top: topLeft.y + (config.initialValue != null ? renderBox.size.height / 2.0 : 0.0)
|
||||
)
|
||||
)
|
||||
.then((T value) {
|
||||
if (config.onSelected != null)
|
||||
config.onSelected(value);
|
||||
});
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
if (config.child == null) {
|
||||
return new IconButton(
|
||||
icon: 'navigation/more_vert',
|
||||
tooltip: config.tooltip,
|
||||
onPressed: () { showButtonMenu(context); }
|
||||
);
|
||||
}
|
||||
return new InkWell(
|
||||
onTap: () { showButtonMenu(context); },
|
||||
child: config.child
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user