Commit 8c98ea37 authored by Hans Muller's avatar Hans Muller

Merge pull request #269 from HansMuller/bottom_sheet_drag

Refactor bottom sheet support, add one to the stocks demo

Factored OverlayRoute out of the modal and persistent bottom sheet classes, since the bottom sheet classes need to drive the performance.

Added a bottom sheet to the stocks demo: long-press on a stock shows a modal bottom sheet.

Made AnimatedModalBarrier public.
parents 0f2a9c40 5e08b98e
...@@ -81,7 +81,7 @@ class StocksAppState extends State<StocksApp> { ...@@ -81,7 +81,7 @@ class StocksAppState extends State<StocksApp> {
if (path.length != 3) if (path.length != 3)
return null; return null;
if (_stocks.containsKey(path[2])) if (_stocks.containsKey(path[2]))
return (RouteArguments args) => new StockSymbolViewer(_stocks[path[2]]); return (RouteArguments args) => new StockSymbolViewer(stock: _stocks[path[2]]);
return null; return null;
} }
return null; return null;
......
...@@ -186,6 +186,10 @@ class StockHomeState extends State<StockHome> { ...@@ -186,6 +186,10 @@ class StockHomeState extends State<StockHome> {
stock.percentChange = 100.0 * (1.0 / stock.lastSale); stock.percentChange = 100.0 * (1.0 / stock.lastSale);
stock.lastSale += 1.0; stock.lastSale += 1.0;
}); });
showModalBottomSheet(
context: context,
child: new StockSymbolViewer(stock: stock, showToolBar: false)
);
}, },
onOpen: (Stock stock, Key arrowKey) { onOpen: (Stock stock, Key arrowKey) {
Set<Key> mostValuableKeys = new Set<Key>(); Set<Key> mostValuableKeys = new Set<Key>();
......
...@@ -5,16 +5,54 @@ ...@@ -5,16 +5,54 @@
part of stocks; part of stocks;
class StockSymbolViewer extends StatelessComponent { class StockSymbolViewer extends StatelessComponent {
StockSymbolViewer(this.stock); StockSymbolViewer({ this.stock, this.showToolBar: true });
final Stock stock; final Stock stock;
final bool showToolBar;
Widget build(BuildContext context) { Widget build(BuildContext context) {
String lastSale = "\$${stock.lastSale.toStringAsFixed(2)}"; String lastSale = "\$${stock.lastSale.toStringAsFixed(2)}";
String changeInPrice = "${stock.percentChange.toStringAsFixed(2)}%"; String changeInPrice = "${stock.percentChange.toStringAsFixed(2)}%";
if (stock.percentChange > 0) if (stock.percentChange > 0)
changeInPrice = "+" + changeInPrice; changeInPrice = "+" + changeInPrice;
TextStyle headings = Theme.of(context).text.body2; TextStyle headings = Theme.of(context).text.body2;
Widget body = new Block(<Widget>[
new Container(
margin: new EdgeDims.all(20.0),
child: new Card(
child: new Container(
padding: new EdgeDims.all(20.0),
child: new Column(<Widget>[
new Row(<Widget>[
new Text(
'${stock.symbol}',
style: Theme.of(context).text.display2
),
new Hero(
tag: StockRowPartKind.arrow,
turns: 2,
child: new StockArrow(percentChange: stock.percentChange)
),
],
justifyContent: FlexJustifyContent.spaceBetween
),
new Text('Last Sale', style: headings),
new Text('$lastSale ($changeInPrice)'),
new Container(
height: 8.0
),
new Text('Market Cap', style: headings),
new Text('${stock.marketCap}'),
])
)
)
)
]);
if (!showToolBar)
return body;
return new Scaffold( return new Scaffold(
toolBar: new ToolBar( toolBar: new ToolBar(
left: new IconButton( left: new IconButton(
...@@ -25,39 +63,7 @@ class StockSymbolViewer extends StatelessComponent { ...@@ -25,39 +63,7 @@ class StockSymbolViewer extends StatelessComponent {
), ),
center: new Text(stock.name) center: new Text(stock.name)
), ),
body: new Block(<Widget>[ body: body
new Container(
margin: new EdgeDims.all(20.0),
child: new Card(
child: new Container(
padding: new EdgeDims.all(20.0),
child: new Column(<Widget>[
new Row(<Widget>[
new Text(
'${stock.symbol}',
style: Theme.of(context).text.display2
),
new Hero(
tag: StockRowPartKind.arrow,
turns: 2,
child: new StockArrow(percentChange: stock.percentChange)
),
],
justifyContent: FlexJustifyContent.spaceBetween
),
new Text('Last Sale', style: headings),
new Text('$lastSale ($changeInPrice)'),
new Container(
height: 8.0
),
new Text('Market Cap', style: headings),
new Text('${stock.marketCap}'),
])
)
)
)
]
)
); );
} }
......
...@@ -15,6 +15,8 @@ import 'material.dart'; ...@@ -15,6 +15,8 @@ import 'material.dart';
const Duration _kBottomSheetDuration = const Duration(milliseconds: 200); const Duration _kBottomSheetDuration = const Duration(milliseconds: 200);
const double _kMinFlingVelocity = 700.0; const double _kMinFlingVelocity = 700.0;
const double _kCloseProgressThreshold = 0.5; const double _kCloseProgressThreshold = 0.5;
const Color _kTransparent = const Color(0x00000000);
const Color _kBarrierColor = Colors.black54;
class _BottomSheetDragController extends StatelessComponent { class _BottomSheetDragController extends StatelessComponent {
_BottomSheetDragController({ _BottomSheetDragController({
...@@ -61,6 +63,29 @@ class _BottomSheetDragController extends StatelessComponent { ...@@ -61,6 +63,29 @@ class _BottomSheetDragController extends StatelessComponent {
} }
} }
class _BottomSheetRoute extends OverlayRoute {
_BottomSheetRoute({ this.completer, this.child });
final Completer completer;
final Widget child;
Performance performance;
void didPush(OverlayState overlay, OverlayEntry insertionPoint) {
performance = new Performance(duration: _kBottomSheetDuration, debugLabel: debugLabel)
..forward();
super.didPush(overlay, insertionPoint);
}
void didPop(dynamic result) {
completer.complete(result);
performance.reverse().then((_) {
super.didPop(result); // clear the overlay entries
});
}
String get debugLabel => '$runtimeType';
String toString() => '$runtimeType(performance: $performance)';
}
class _ModalBottomSheet extends StatefulComponent { class _ModalBottomSheet extends StatefulComponent {
_ModalBottomSheet({ Key key, this.route }) : super(key: key); _ModalBottomSheet({ Key key, this.route }) : super(key: key);
...@@ -97,7 +122,7 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> { ...@@ -97,7 +122,7 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> {
return new GestureDetector( return new GestureDetector(
onTap: () { Navigator.of(context).pop(); }, onTap: () { Navigator.of(context).pop(); },
child: new BuilderTransition( child: new BuilderTransition(
performance: config.route._performance, performance: config.route.performance,
variables: <AnimatedValue<double>>[_layout.childTop], variables: <AnimatedValue<double>>[_layout.childTop],
builder: (BuildContext context) { builder: (BuildContext context) {
return new ClipRect( return new ClipRect(
...@@ -105,7 +130,7 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> { ...@@ -105,7 +130,7 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> {
delegate: _layout, delegate: _layout,
token: _layout.childTop.value, token: _layout.childTop.value,
child: new _BottomSheetDragController( child: new _BottomSheetDragController(
performance: config.route._performance, performance: config.route.performance,
child: new Material(child: config.route.child), child: new Material(child: config.route.child),
childHeight: _layout.childTop.end childHeight: _layout.childTop.end
) )
...@@ -117,29 +142,28 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> { ...@@ -117,29 +142,28 @@ class _ModalBottomSheetState extends State<_ModalBottomSheet> {
} }
} }
class _ModalBottomSheetRoute extends ModalRoute { class _ModalBottomSheetRoute extends _BottomSheetRoute {
_ModalBottomSheetRoute({ this.completer, this.child }); _ModalBottomSheetRoute({ Completer completer, Widget child })
: super(completer: completer, child: child);
final Completer completer;
final Widget child;
bool get opaque => false;
Duration get transitionDuration => _kBottomSheetDuration;
Performance _performance;
Performance createPerformance() { Widget _buildModalBarrier(BuildContext context) {
_performance = super.createPerformance(); return new AnimatedModalBarrier(
return _performance; color: new AnimatedColorValue(_kTransparent, end: _kBarrierColor, curve: Curves.ease),
performance: performance
);
} }
Color get barrierColor => Colors.black54; Widget _buildBottomSheet(BuildContext context) {
Widget buildModalWidget(BuildContext context) => new _ModalBottomSheet(route: this); return new Focus(
key: new GlobalObjectKey(this),
void didPop([dynamic result]) { child: new _ModalBottomSheet(route: this)
completer.complete(result); );
super.didPop(result);
} }
List<WidgetBuilder> get builders => <WidgetBuilder>[
_buildModalBarrier,
_buildBottomSheet,
];
} }
Future showModalBottomSheet({ BuildContext context, Widget child }) { Future showModalBottomSheet({ BuildContext context, Widget child }) {
...@@ -155,7 +179,7 @@ Future showModalBottomSheet({ BuildContext context, Widget child }) { ...@@ -155,7 +179,7 @@ Future showModalBottomSheet({ BuildContext context, Widget child }) {
class _PersistentBottomSheet extends StatefulComponent { class _PersistentBottomSheet extends StatefulComponent {
_PersistentBottomSheet({ Key key, this.route }) : super(key: key); _PersistentBottomSheet({ Key key, this.route }) : super(key: key);
final _PersistentBottomSheetRoute route; final _BottomSheetRoute route;
_PersistentBottomSheetState createState() => new _PersistentBottomSheetState(); _PersistentBottomSheetState createState() => new _PersistentBottomSheetState();
} }
...@@ -175,7 +199,7 @@ class _PersistentBottomSheetState extends State<_PersistentBottomSheet> { ...@@ -175,7 +199,7 @@ class _PersistentBottomSheetState extends State<_PersistentBottomSheet> {
alignment: new AnimatedValue<FractionalOffset>(const FractionalOffset(0.0, 0.0)), alignment: new AnimatedValue<FractionalOffset>(const FractionalOffset(0.0, 0.0)),
heightFactor: new AnimatedValue<double>(0.0, end: 1.0), heightFactor: new AnimatedValue<double>(0.0, end: 1.0),
child: new _BottomSheetDragController( child: new _BottomSheetDragController(
performance: config.route._performance, performance: config.route.performance,
childHeight: _childHeight, childHeight: _childHeight,
child: new Material( child: new Material(
child: new SizeObserver(child: config.route.child, onSizeChanged: _updateChildHeight) child: new SizeObserver(child: config.route.child, onSizeChanged: _updateChildHeight)
...@@ -185,32 +209,11 @@ class _PersistentBottomSheetState extends State<_PersistentBottomSheet> { ...@@ -185,32 +209,11 @@ class _PersistentBottomSheetState extends State<_PersistentBottomSheet> {
} }
} }
class _PersistentBottomSheetRoute extends TransitionRoute {
_PersistentBottomSheetRoute({ this.completer, this.child });
final Widget child;
final Completer completer;
bool get opaque => false;
Duration get transitionDuration => _kBottomSheetDuration;
Performance _performance;
Performance createPerformance() {
_performance = super.createPerformance();
return _performance;
}
void didPop([dynamic result]) {
completer.complete(result);
super.didPop(result);
}
}
Future showBottomSheet({ BuildContext context, GlobalKey<PlaceholderState> placeholderKey, Widget child }) { Future showBottomSheet({ BuildContext context, GlobalKey<PlaceholderState> placeholderKey, Widget child }) {
assert(child != null); assert(child != null);
assert(placeholderKey != null); assert(placeholderKey != null);
final Completer completer = new Completer(); final Completer completer = new Completer();
_PersistentBottomSheetRoute route = new _PersistentBottomSheetRoute(child: child, completer: completer); _BottomSheetRoute route = new _BottomSheetRoute(child: child, completer: completer);
placeholderKey.currentState.child = new _PersistentBottomSheet(route: route); placeholderKey.currentState.child = new _PersistentBottomSheet(route: route);
Navigator.of(context).pushEphemeral(route); Navigator.of(context).pushEphemeral(route);
return completer.future; return completer.future;
......
...@@ -39,8 +39,8 @@ class ModalBarrier extends StatelessComponent { ...@@ -39,8 +39,8 @@ class ModalBarrier extends StatelessComponent {
} }
} }
class _AnimatedModalBarrier extends StatelessComponent { class AnimatedModalBarrier extends StatelessComponent {
_AnimatedModalBarrier({ AnimatedModalBarrier({
Key key, Key key,
this.color, this.color,
this.performance this.performance
...@@ -108,7 +108,7 @@ abstract class ModalRoute extends TransitionRoute { ...@@ -108,7 +108,7 @@ abstract class ModalRoute extends TransitionRoute {
Widget buildModalWidget(BuildContext context); Widget buildModalWidget(BuildContext context);
Widget _buildModalBarrier(BuildContext context) { Widget _buildModalBarrier(BuildContext context) {
return new _AnimatedModalBarrier( return new AnimatedModalBarrier(
color: new AnimatedColorValue(_kTransparent, end: barrierColor, curve: Curves.ease), color: new AnimatedColorValue(_kTransparent, end: barrierColor, curve: Curves.ease),
performance: performance performance: performance
); );
......
...@@ -29,6 +29,7 @@ void main() { ...@@ -29,6 +29,7 @@ void main() {
tester.tap(tester.findText('BottomSheet')); tester.tap(tester.findText('BottomSheet'));
tester.pump(); // bottom sheet dismiss animation starts tester.pump(); // bottom sheet dismiss animation starts
tester.pump(new Duration(seconds: 1)); // animation done tester.pump(new Duration(seconds: 1)); // animation done
tester.pump(new Duration(seconds: 2)); // rebuild frame
expect(tester.findText('BottomSheet'), isNull); expect(tester.findText('BottomSheet'), isNull);
showModalBottomSheet(context: context, child: new Text('BottomSheet')); showModalBottomSheet(context: context, child: new Text('BottomSheet'));
...@@ -40,6 +41,7 @@ void main() { ...@@ -40,6 +41,7 @@ void main() {
tester.tapAt(new Point(20.0, 20.0)); tester.tapAt(new Point(20.0, 20.0));
tester.pump(); // bottom sheet dismiss animation starts tester.pump(); // bottom sheet dismiss animation starts
tester.pump(new Duration(seconds: 1)); // animation done tester.pump(new Duration(seconds: 1)); // animation done
tester.pump(new Duration(seconds: 2)); // rebuild frame
expect(tester.findText('BottomSheet'), isNull); expect(tester.findText('BottomSheet'), isNull);
}); });
}); });
......
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