From 4982c553b891790b285264c7e787492c58510b4a Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Mon, 7 Dec 2015 14:58:27 -0800 Subject: [PATCH] Material gallery crashes when you press the drop-down button Now use use the route's getPosition function to position the drop-down menu. Also, fix a number of other related bugs that blocked the dropdown button from working correctly. The dropdown menu still has the following issues: 1) In the exit animation, the background of the menu disappears too quickly because of incorrect paint bounds computations in the layer tree. 2) The drop down menu isn't positioned correctly after the device rotates. We'll need to address this issue in a separate patch. Fixes #630 --- .../flutter/lib/src/material/dropdown.dart | 132 ++++++++++-------- .../flutter/lib/src/material/material.dart | 30 ++-- .../flutter/lib/src/material/popup_menu.dart | 4 + packages/flutter/lib/src/widgets/routes.dart | 4 +- 4 files changed, 101 insertions(+), 69 deletions(-) diff --git a/packages/flutter/lib/src/material/dropdown.dart b/packages/flutter/lib/src/material/dropdown.dart index 18b3e144d3..0d0657488a 100644 --- a/packages/flutter/lib/src/material/dropdown.dart +++ b/packages/flutter/lib/src/material/dropdown.dart @@ -13,6 +13,7 @@ import 'icon.dart'; import 'ink_well.dart'; import 'shadows.dart'; import 'theme.dart'; +import 'material.dart'; const Duration _kDropDownMenuDuration = const Duration(milliseconds: 300); const double _kMenuItemHeight = 48.0; @@ -100,51 +101,42 @@ class _DropDownMenu extends StatusTransitionComponent { final AnimatedValue menuOpacity = new AnimatedValue(0.0, end: 1.0, - curve: new Interval(0.0, 0.25), - reverseCurve: new Interval(0.75, 1.0) + curve: const Interval(0.0, 0.25), + reverseCurve: const Interval(0.75, 1.0) ); final AnimatedValue menuTop = new AnimatedValue(route.rect.top, 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) ); final AnimatedValue menuBottom = new AnimatedValue(route.rect.bottom, 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) ); - final RenderBox renderBox = route.navigator.context.findRenderObject(); - final Size navigatorSize = renderBox.size; - final RelativeRect menuRect = new RelativeRect.fromSize(route.rect, navigatorSize); - - return new Positioned( - top: menuRect.top - (route.selectedIndex * route.rect.height), - right: menuRect.right, - left: menuRect.left, - child: new Focus( - key: new GlobalObjectKey(route), - child: new FadeTransition( - performance: route.performance, - opacity: menuOpacity, - child: new BuilderTransition( - performance: route.performance, - variables: >[menuTop, menuBottom], - builder: (BuildContext context) { - return new CustomPaint( - painter: new _DropDownMenuPainter( - color: Theme.of(context).canvasColor, - elevation: route.elevation, - menuTop: menuTop.value, - menuBottom: menuBottom.value, - renderBox: context.findRenderObject() - ), - child: new Block(children) - ); - } - ) - ) + return new FadeTransition( + performance: route.performance, + opacity: menuOpacity, + child: new BuilderTransition( + performance: route.performance, + variables: >[menuTop, menuBottom], + builder: (BuildContext context) { + return new CustomPaint( + painter: new _DropDownMenuPainter( + color: Theme.of(context).canvasColor, + elevation: route.elevation, + menuTop: menuTop.value, + menuBottom: menuBottom.value, + renderBox: context.findRenderObject() + ), + child: new Material( + type: MaterialType.transparency, + child: new Block(children) + ) + ); + } ) ); } @@ -168,6 +160,17 @@ class _DropDownRoute extends PopupRoute { bool get barrierDismissable => true; 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) { return new _DropDownMenu(route: this); } @@ -198,7 +201,7 @@ class DropDownMenuItem extends StatelessComponent { } } -class DropDownButton extends StatelessComponent { +class DropDownButton extends StatefulComponent { DropDownButton({ Key key, this.items, @@ -212,40 +215,62 @@ class DropDownButton extends StatelessComponent { final ValueChanged onChanged; final int elevation; - void _showDropDown(BuildContext context, int selectedIndex, GlobalKey indexedStackKey) { + _DropDownButtonState createState() => new _DropDownButtonState(); +} + +class _DropDownButtonState extends State> { + final GlobalKey indexedStackKey = new GlobalKey(debugLabel: 'DropDownButton.IndexedStack'); + + void initState() { + super.initState(); + _updateSelectedIndex(); + } + + void didUpdateConfig(DropDownButton 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 Rect rect = renderBox.localToGlobal(Point.origin) & renderBox.size; final Completer completer = new Completer(); Navigator.push(context, new _DropDownRoute( completer: completer, - items: items, - selectedIndex: selectedIndex, + items: config.items, + selectedIndex: _selectedIndex, rect: _kMenuHorizontalPadding.inflateRect(rect), - elevation: elevation + elevation: config.elevation )); completer.future.then((T newValue) { - if (onChanged != null) - onChanged(newValue); + if (!mounted) + return; + if (config.onChanged != null) + config.onChanged(newValue); }); } 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( + onTap: _handleTap, child: new Container( decoration: new BoxDecoration(border: _kDropDownUnderline), child: new Row([ new IndexedStack( - items, + config.items, key: indexedStackKey, - index: selectedIndex, + index: _selectedIndex, alignment: const FractionalOffset(0.5, 0.0) ), new Container( @@ -255,10 +280,7 @@ class DropDownButton extends StatelessComponent { ], justifyContent: FlexJustifyContent.collapse ) - ), - onTap: () { - _showDropDown(context, selectedIndex, indexedStackKey); - } + ) ); } } diff --git a/packages/flutter/lib/src/material/material.dart b/packages/flutter/lib/src/material/material.dart index e1a81a4258..b20bd447d4 100644 --- a/packages/flutter/lib/src/material/material.dart +++ b/packages/flutter/lib/src/material/material.dart @@ -25,7 +25,10 @@ enum MaterialType { circle, /// 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 kMaterialEdges = const { @@ -33,6 +36,7 @@ const Map kMaterialEdges = const { MaterialType.card: 2.0, MaterialType.circle: null, MaterialType.button: 2.0, + MaterialType.transparency: null, }; abstract class InkSplash { @@ -141,17 +145,19 @@ class _MaterialState extends State { child: contents ); } - contents = new AnimatedContainer( - curve: Curves.ease, - duration: kThemeChangeDuration, - decoration: new BoxDecoration( - backgroundColor: backgroundColor, - borderRadius: kMaterialEdges[config.type], - boxShadow: config.elevation == 0 ? null : elevationToShadow[config.elevation], - shape: config.type == MaterialType.circle ? Shape.circle : Shape.rectangle - ), - child: contents - ); + if (config.type != MaterialType.transparency) { + contents = new AnimatedContainer( + curve: Curves.ease, + duration: kThemeChangeDuration, + decoration: new BoxDecoration( + backgroundColor: backgroundColor, + borderRadius: kMaterialEdges[config.type], + boxShadow: config.elevation == 0 ? null : elevationToShadow[config.elevation], + shape: config.type == MaterialType.circle ? Shape.circle : Shape.rectangle + ), + child: contents + ); + } return contents; } } diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart index ca9189550d..6ef058839a 100644 --- a/packages/flutter/lib/src/material/popup_menu.dart +++ b/packages/flutter/lib/src/material/popup_menu.dart @@ -123,6 +123,10 @@ class _PopupMenuRoute extends PopupRoute { final List> items; final int elevation; + ModalPosition getPosition(BuildContext context) { + return position; + } + PerformanceView createPerformance() { return new CurvedPerformance( super.createPerformance(), diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart index b7f35c5909..843b57d665 100644 --- a/packages/flutter/lib/src/widgets/routes.dart +++ b/packages/flutter/lib/src/widgets/routes.dart @@ -349,7 +349,7 @@ class _ModalScopeState extends State<_ModalScope> { ); } contents = new RepaintBoundary(child: contents); - ModalPosition position = config.route.position; + ModalPosition position = config.route.getPosition(context); if (position == null) return contents; return new Positioned( @@ -388,7 +388,7 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute null; + ModalPosition getPosition(BuildContext context) => null; Widget buildPage(BuildContext context, PerformanceView performance, PerformanceView forwardPerformance); Widget buildTransitions(BuildContext context, PerformanceView performance, PerformanceView forwardPerformance, Widget child) { return child;