Commit 8dd8c203 authored by Adam Barth's avatar Adam Barth

Merge pull request #803 from abarth/dropdown

Material gallery crashes when you press the drop-down button
parents aaef5045 4982c553
...@@ -13,6 +13,7 @@ import 'icon.dart'; ...@@ -13,6 +13,7 @@ import 'icon.dart';
import 'ink_well.dart'; import 'ink_well.dart';
import 'shadows.dart'; import 'shadows.dart';
import 'theme.dart'; import 'theme.dart';
import 'material.dart';
const Duration _kDropDownMenuDuration = const Duration(milliseconds: 300); const Duration _kDropDownMenuDuration = const Duration(milliseconds: 300);
const double _kMenuItemHeight = 48.0; const double _kMenuItemHeight = 48.0;
...@@ -100,51 +101,42 @@ class _DropDownMenu<T> extends StatusTransitionComponent { ...@@ -100,51 +101,42 @@ class _DropDownMenu<T> extends StatusTransitionComponent {
final AnimatedValue<double> menuOpacity = new AnimatedValue<double>(0.0, final AnimatedValue<double> menuOpacity = new AnimatedValue<double>(0.0,
end: 1.0, end: 1.0,
curve: new Interval(0.0, 0.25), curve: const Interval(0.0, 0.25),
reverseCurve: new Interval(0.75, 1.0) reverseCurve: const Interval(0.75, 1.0)
); );
final AnimatedValue<double> menuTop = new AnimatedValue<double>(route.rect.top, final AnimatedValue<double> menuTop = new AnimatedValue<double>(route.rect.top,
end: route.rect.top - route.selectedIndex * route.rect.height, end: route.rect.top - route.selectedIndex * route.rect.height,
curve: new Interval(0.25, 0.5), curve: const Interval(0.25, 0.5),
reverseCurve: const Interval(0.0, 0.001) reverseCurve: const Interval(0.0, 0.001)
); );
final AnimatedValue<double> menuBottom = new AnimatedValue<double>(route.rect.bottom, final AnimatedValue<double> menuBottom = new AnimatedValue<double>(route.rect.bottom,
end: menuTop.end + route.items.length * route.rect.height, end: menuTop.end + route.items.length * route.rect.height,
curve: new Interval(0.25, 0.5), curve: const Interval(0.25, 0.5),
reverseCurve: const Interval(0.0, 0.001) reverseCurve: const Interval(0.0, 0.001)
); );
final RenderBox renderBox = route.navigator.context.findRenderObject(); return new FadeTransition(
final Size navigatorSize = renderBox.size; performance: route.performance,
final RelativeRect menuRect = new RelativeRect.fromSize(route.rect, navigatorSize); opacity: menuOpacity,
child: new BuilderTransition(
return new Positioned( performance: route.performance,
top: menuRect.top - (route.selectedIndex * route.rect.height), variables: <AnimatedValue<double>>[menuTop, menuBottom],
right: menuRect.right, builder: (BuildContext context) {
left: menuRect.left, return new CustomPaint(
child: new Focus( painter: new _DropDownMenuPainter(
key: new GlobalObjectKey(route), color: Theme.of(context).canvasColor,
child: new FadeTransition( elevation: route.elevation,
performance: route.performance, menuTop: menuTop.value,
opacity: menuOpacity, menuBottom: menuBottom.value,
child: new BuilderTransition( renderBox: context.findRenderObject()
performance: route.performance, ),
variables: <AnimatedValue<double>>[menuTop, menuBottom], child: new Material(
builder: (BuildContext context) { type: MaterialType.transparency,
return new CustomPaint( child: new Block(children)
painter: new _DropDownMenuPainter( )
color: Theme.of(context).canvasColor, );
elevation: route.elevation, }
menuTop: menuTop.value,
menuBottom: menuBottom.value,
renderBox: context.findRenderObject()
),
child: new Block(children)
);
}
)
)
) )
); );
} }
...@@ -168,6 +160,17 @@ class _DropDownRoute<T> extends PopupRoute<T> { ...@@ -168,6 +160,17 @@ class _DropDownRoute<T> extends PopupRoute<T> {
bool get barrierDismissable => true; bool get barrierDismissable => true;
Color get barrierColor => null; Color get barrierColor => null;
ModalPosition getPosition(BuildContext context) {
RenderBox overlayBox = Overlay.of(context).context.findRenderObject();
Size overlaySize = overlayBox.size;
RelativeRect menuRect = new RelativeRect.fromSize(rect, overlaySize);
return new ModalPosition(
top: menuRect.top - selectedIndex * rect.height,
left: menuRect.left,
right: menuRect.right
);
}
Widget buildPage(BuildContext context, PerformanceView performance, PerformanceView forwardPerformance) { Widget buildPage(BuildContext context, PerformanceView performance, PerformanceView forwardPerformance) {
return new _DropDownMenu(route: this); return new _DropDownMenu(route: this);
} }
...@@ -198,7 +201,7 @@ class DropDownMenuItem<T> extends StatelessComponent { ...@@ -198,7 +201,7 @@ class DropDownMenuItem<T> extends StatelessComponent {
} }
} }
class DropDownButton<T> extends StatelessComponent { class DropDownButton<T> extends StatefulComponent {
DropDownButton({ DropDownButton({
Key key, Key key,
this.items, this.items,
...@@ -212,40 +215,62 @@ class DropDownButton<T> extends StatelessComponent { ...@@ -212,40 +215,62 @@ class DropDownButton<T> extends StatelessComponent {
final ValueChanged<T> onChanged; final ValueChanged<T> onChanged;
final int elevation; final int elevation;
void _showDropDown(BuildContext context, int selectedIndex, GlobalKey indexedStackKey) { _DropDownButtonState<T> createState() => new _DropDownButtonState<T>();
}
class _DropDownButtonState<T> extends State<DropDownButton<T>> {
final GlobalKey indexedStackKey = new GlobalKey(debugLabel: 'DropDownButton.IndexedStack');
void initState() {
super.initState();
_updateSelectedIndex();
}
void didUpdateConfig(DropDownButton<T> oldConfig) {
if (config.items[_selectedIndex].value != config.value)
_updateSelectedIndex();
}
int _selectedIndex;
void _updateSelectedIndex() {
for (int itemIndex = 0; itemIndex < config.items.length; itemIndex++) {
if (config.items[itemIndex].value == config.value) {
_selectedIndex = itemIndex;
return;
}
}
}
void _handleTap() {
final RenderBox renderBox = indexedStackKey.currentContext.findRenderObject(); final RenderBox renderBox = indexedStackKey.currentContext.findRenderObject();
final Rect rect = renderBox.localToGlobal(Point.origin) & renderBox.size; final Rect rect = renderBox.localToGlobal(Point.origin) & renderBox.size;
final Completer completer = new Completer<T>(); final Completer completer = new Completer<T>();
Navigator.push(context, new _DropDownRoute<T>( Navigator.push(context, new _DropDownRoute<T>(
completer: completer, completer: completer,
items: items, items: config.items,
selectedIndex: selectedIndex, selectedIndex: _selectedIndex,
rect: _kMenuHorizontalPadding.inflateRect(rect), rect: _kMenuHorizontalPadding.inflateRect(rect),
elevation: elevation elevation: config.elevation
)); ));
completer.future.then((T newValue) { completer.future.then((T newValue) {
if (onChanged != null) if (!mounted)
onChanged(newValue); return;
if (config.onChanged != null)
config.onChanged(newValue);
}); });
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
GlobalKey indexedStackKey = new GlobalKey(debugLabel: 'DropDownButton.IndexedStack');
int selectedIndex = 0;
for (int itemIndex = 0; itemIndex < items.length; itemIndex++) {
if (items[itemIndex].value == value) {
selectedIndex = itemIndex;
break;
}
}
return new GestureDetector( return new GestureDetector(
onTap: _handleTap,
child: new Container( child: new Container(
decoration: new BoxDecoration(border: _kDropDownUnderline), decoration: new BoxDecoration(border: _kDropDownUnderline),
child: new Row(<Widget>[ child: new Row(<Widget>[
new IndexedStack( new IndexedStack(
items, config.items,
key: indexedStackKey, key: indexedStackKey,
index: selectedIndex, index: _selectedIndex,
alignment: const FractionalOffset(0.5, 0.0) alignment: const FractionalOffset(0.5, 0.0)
), ),
new Container( new Container(
...@@ -255,10 +280,7 @@ class DropDownButton<T> extends StatelessComponent { ...@@ -255,10 +280,7 @@ class DropDownButton<T> extends StatelessComponent {
], ],
justifyContent: FlexJustifyContent.collapse justifyContent: FlexJustifyContent.collapse
) )
), )
onTap: () {
_showDropDown(context, selectedIndex, indexedStackKey);
}
); );
} }
} }
...@@ -25,7 +25,10 @@ enum MaterialType { ...@@ -25,7 +25,10 @@ enum MaterialType {
circle, circle,
/// Rounded edges, no color by default (used for MaterialButton buttons). /// Rounded edges, no color by default (used for MaterialButton buttons).
button button,
/// A transparent piece of material that draws ink splashes and highlights.
transparency
} }
const Map<MaterialType, double> kMaterialEdges = const <MaterialType, double>{ const Map<MaterialType, double> kMaterialEdges = const <MaterialType, double>{
...@@ -33,6 +36,7 @@ const Map<MaterialType, double> kMaterialEdges = const <MaterialType, double>{ ...@@ -33,6 +36,7 @@ const Map<MaterialType, double> kMaterialEdges = const <MaterialType, double>{
MaterialType.card: 2.0, MaterialType.card: 2.0,
MaterialType.circle: null, MaterialType.circle: null,
MaterialType.button: 2.0, MaterialType.button: 2.0,
MaterialType.transparency: null,
}; };
abstract class InkSplash { abstract class InkSplash {
...@@ -141,17 +145,19 @@ class _MaterialState extends State<Material> { ...@@ -141,17 +145,19 @@ class _MaterialState extends State<Material> {
child: contents child: contents
); );
} }
contents = new AnimatedContainer( if (config.type != MaterialType.transparency) {
curve: Curves.ease, contents = new AnimatedContainer(
duration: kThemeChangeDuration, curve: Curves.ease,
decoration: new BoxDecoration( duration: kThemeChangeDuration,
backgroundColor: backgroundColor, decoration: new BoxDecoration(
borderRadius: kMaterialEdges[config.type], backgroundColor: backgroundColor,
boxShadow: config.elevation == 0 ? null : elevationToShadow[config.elevation], borderRadius: kMaterialEdges[config.type],
shape: config.type == MaterialType.circle ? Shape.circle : Shape.rectangle boxShadow: config.elevation == 0 ? null : elevationToShadow[config.elevation],
), shape: config.type == MaterialType.circle ? Shape.circle : Shape.rectangle
child: contents ),
); child: contents
);
}
return contents; return contents;
} }
} }
......
...@@ -123,6 +123,10 @@ class _PopupMenuRoute<T> extends PopupRoute<T> { ...@@ -123,6 +123,10 @@ class _PopupMenuRoute<T> extends PopupRoute<T> {
final List<PopupMenuItem<T>> items; final List<PopupMenuItem<T>> items;
final int elevation; final int elevation;
ModalPosition getPosition(BuildContext context) {
return position;
}
PerformanceView createPerformance() { PerformanceView createPerformance() {
return new CurvedPerformance( return new CurvedPerformance(
super.createPerformance(), super.createPerformance(),
......
...@@ -359,7 +359,7 @@ class _ModalScopeState extends State<_ModalScope> { ...@@ -359,7 +359,7 @@ class _ModalScopeState extends State<_ModalScope> {
); );
} }
contents = new RepaintBoundary(child: contents); contents = new RepaintBoundary(child: contents);
ModalPosition position = config.route.position; ModalPosition position = config.route.getPosition(context);
if (position == null) if (position == null)
return contents; return contents;
return new Positioned( return new Positioned(
...@@ -398,7 +398,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -398,7 +398,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
// The API for subclasses to override - used by _ModalScope // The API for subclasses to override - used by _ModalScope
ModalPosition get position => null; ModalPosition getPosition(BuildContext context) => null;
Widget buildPage(BuildContext context, PerformanceView performance, PerformanceView forwardPerformance); Widget buildPage(BuildContext context, PerformanceView performance, PerformanceView forwardPerformance);
Widget buildTransitions(BuildContext context, PerformanceView performance, PerformanceView forwardPerformance, Widget child) { Widget buildTransitions(BuildContext context, PerformanceView performance, PerformanceView forwardPerformance, Widget child) {
return child; return child;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment