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,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(),
......
...@@ -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