Commit 4982c553 authored by Adam Barth's avatar Adam Barth

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
parent 2305d019
...@@ -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,32 +101,22 @@ class _DropDownMenu<T> extends StatusTransitionComponent { ...@@ -100,32 +101,22 @@ 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;
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, performance: route.performance,
opacity: menuOpacity, opacity: menuOpacity,
child: new BuilderTransition( child: new BuilderTransition(
...@@ -140,12 +131,13 @@ class _DropDownMenu<T> extends StatusTransitionComponent { ...@@ -140,12 +131,13 @@ class _DropDownMenu<T> extends StatusTransitionComponent {
menuBottom: menuBottom.value, menuBottom: menuBottom.value,
renderBox: context.findRenderObject() renderBox: context.findRenderObject()
), ),
child: new Material(
type: MaterialType.transparency,
child: new Block(children) 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,6 +145,7 @@ class _MaterialState extends State<Material> { ...@@ -141,6 +145,7 @@ class _MaterialState extends State<Material> {
child: contents child: contents
); );
} }
if (config.type != MaterialType.transparency) {
contents = new AnimatedContainer( contents = new AnimatedContainer(
curve: Curves.ease, curve: Curves.ease,
duration: kThemeChangeDuration, duration: kThemeChangeDuration,
...@@ -152,6 +157,7 @@ class _MaterialState extends State<Material> { ...@@ -152,6 +157,7 @@ class _MaterialState extends State<Material> {
), ),
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(),
......
...@@ -349,7 +349,7 @@ class _ModalScopeState extends State<_ModalScope> { ...@@ -349,7 +349,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(
...@@ -388,7 +388,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T ...@@ -388,7 +388,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