Commit 23d7a23e authored by Hans Muller's avatar Hans Muller

Added AppBarBehavior.under, etc

parent bedd8e91
...@@ -72,8 +72,10 @@ class FlexibleSpaceDemo extends StatefulWidget { ...@@ -72,8 +72,10 @@ class FlexibleSpaceDemo extends StatefulWidget {
} }
class FlexibleSpaceDemoState extends State<FlexibleSpaceDemo> { class FlexibleSpaceDemoState extends State<FlexibleSpaceDemo> {
final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>();
final double appBarHeight = 256.0; final double appBarHeight = 256.0;
final Key scrollableKey = new UniqueKey(); final Key scrollableKey = new UniqueKey();
AppBarBehavior _appBarBehavior = AppBarBehavior.scroll;
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new Theme( return new Theme(
...@@ -82,18 +84,37 @@ class FlexibleSpaceDemoState extends State<FlexibleSpaceDemo> { ...@@ -82,18 +84,37 @@ class FlexibleSpaceDemoState extends State<FlexibleSpaceDemo> {
primarySwatch: Colors.indigo primarySwatch: Colors.indigo
), ),
child: new Scaffold( child: new Scaffold(
key: scaffoldKey,
appBarHeight: appBarHeight, appBarHeight: appBarHeight,
scrollableKey: scrollableKey, scrollableKey: scrollableKey,
appBarBehavior: AppBarBehavior.scroll, appBarBehavior: _appBarBehavior,
appBar: new AppBar( appBar: new AppBar(
actions: <Widget>[ actions: <Widget>[
new IconButton( new IconButton(
icon: Icons.create, icon: Icons.create,
tooltip: 'Search' tooltip: 'Search',
onPressed: () {
scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text('Not supported.')
));
}
), ),
new IconButton( new PopupMenuButton<AppBarBehavior>(
icon: Icons.more_vert, onSelected: (AppBarBehavior value) {
tooltip: 'Show menu' setState(() {
_appBarBehavior = value;
});
},
items: <PopupMenuItem<AppBarBehavior>>[
new PopupMenuItem<AppBarBehavior>(
value: AppBarBehavior.scroll,
child: new Text('AppBar scrolls away')
),
new PopupMenuItem<AppBarBehavior>(
value: AppBarBehavior.under,
child: new Text('AppBar stays put')
)
]
) )
], ],
flexibleSpace: (BuildContext context) { flexibleSpace: (BuildContext context) {
......
...@@ -37,7 +37,7 @@ class GallerySection extends StatelessWidget { ...@@ -37,7 +37,7 @@ class GallerySection extends StatelessWidget {
data: theme, data: theme,
child: new Scaffold( child: new Scaffold(
appBarHeight: appBarHeight, appBarHeight: appBarHeight,
appBarBehavior: AppBarBehavior.scroll, appBarBehavior: AppBarBehavior.under,
scrollableKey: scrollableKey, scrollableKey: scrollableKey,
appBar: new AppBar( appBar: new AppBar(
flexibleSpace: (BuildContext context) => new FlexibleSpaceBar(title: new Text(title)) flexibleSpace: (BuildContext context) => new FlexibleSpaceBar(title: new Text(title))
......
...@@ -117,7 +117,7 @@ class AppBar extends StatelessWidget { ...@@ -117,7 +117,7 @@ class AppBar extends StatelessWidget {
// If the toolBar's height shrinks below toolBarHeight, it will be clipped and bottom // If the toolBar's height shrinks below toolBarHeight, it will be clipped and bottom
// justified. This is so that the toolbar appears to move upwards as its height is reduced. // justified. This is so that the toolbar appears to move upwards as its height is reduced.
final double toolBarHeight = kAppBarHeight + combinedPadding.top + combinedPadding.bottom; final double toolBarHeight = kToolBarHeight + combinedPadding.top + combinedPadding.bottom;
final Widget toolBar = new ConstrainedBox( final Widget toolBar = new ConstrainedBox(
constraints: new BoxConstraints(maxHeight: toolBarHeight), constraints: new BoxConstraints(maxHeight: toolBarHeight),
child: new Padding( child: new Padding(
......
...@@ -9,7 +9,7 @@ import 'package:flutter/widgets.dart'; ...@@ -9,7 +9,7 @@ import 'package:flutter/widgets.dart';
// Mobile Landscape: 48dp // Mobile Landscape: 48dp
// Mobile Portrait: 56dp // Mobile Portrait: 56dp
// Tablet/Desktop: 64dp // Tablet/Desktop: 64dp
const double kAppBarHeight = 56.0; const double kToolBarHeight = 56.0;
const double kExtendedAppBarHeight = 128.0; const double kExtendedAppBarHeight = 128.0;
const double kTextTabBarHeight = 48.0; const double kTextTabBarHeight = 48.0;
......
...@@ -27,7 +27,7 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> { ...@@ -27,7 +27,7 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
final double appBarHeight = Scaffold.of(context).appBarHeight; final double appBarHeight = Scaffold.of(context).appBarHeight;
final Animation<double> animation = Scaffold.of(context).appBarAnimation; final Animation<double> animation = Scaffold.of(context).appBarAnimation;
final EdgeInsets toolBarPadding = MediaQuery.of(context)?.padding ?? EdgeInsets.zero; final EdgeInsets toolBarPadding = MediaQuery.of(context)?.padding ?? EdgeInsets.zero;
final double toolBarHeight = kAppBarHeight + toolBarPadding.top; final double toolBarHeight = kToolBarHeight + toolBarPadding.top;
final List<Widget> children = <Widget>[]; final List<Widget> children = <Widget>[];
// background image // background image
...@@ -63,7 +63,7 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> { ...@@ -63,7 +63,7 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
color: titleStyle.color.withAlpha(new Tween<double>(begin: 255.0, end: 0.0).evaluate(opacityCurve).toInt()) color: titleStyle.color.withAlpha(new Tween<double>(begin: 255.0, end: 0.0).evaluate(opacityCurve).toInt())
); );
final double yAlignStart = 1.0; final double yAlignStart = 1.0;
final double yAlignEnd = (toolBarPadding.top + kAppBarHeight / 2.0) / toolBarHeight; final double yAlignEnd = (toolBarPadding.top + kToolBarHeight / 2.0) / toolBarHeight;
final double scaleAndAlignEnd = (appBarHeight - toolBarHeight) / appBarHeight; final double scaleAndAlignEnd = (appBarHeight - toolBarHeight) / appBarHeight;
final CurvedAnimation scaleAndAlignCurve = new CurvedAnimation( final CurvedAnimation scaleAndAlignCurve = new CurvedAnimation(
parent: animation, parent: animation,
......
...@@ -20,9 +20,25 @@ import 'snack_bar.dart'; ...@@ -20,9 +20,25 @@ import 'snack_bar.dart';
const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be device dependent const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be device dependent
const Duration _kFloatingActionButtonSegue = const Duration(milliseconds: 400); const Duration _kFloatingActionButtonSegue = const Duration(milliseconds: 400);
/// The Scaffold's appbar is the toolbar, tabbar, and the "flexible space" that's
/// stacked behind them. The Scaffold's appBarBehavior defines how the appbar
/// responds to scrolling the application.
enum AppBarBehavior { enum AppBarBehavior {
/// The tool bar's layout does not respond to scrolling.
anchor, anchor,
/// The tool bar's appearance and layout depend on the scrollOffset of the
/// Scrollable identified by the Scaffold's scrollableKey. With the scrollOffset
/// at 0.0, scrolling downwards causes the toolbar's flexible space to shrink,
/// and then the entire toolbar fade outs and scrolls off the top of the screen.
/// Scrolling upwards always causes the toolbar to reappear.
scroll, scroll,
/// The tool bar's appearance and layout depend on the scrollOffset of the
/// Scrollable identified by the Scaffold's scrollableKey. With the scrollOffset
/// at 0.0, Scrolling downwards causes the toolbar's flexible space to shrink.
/// Other than that, the toolbar remains anchored at the top.
under,
} }
enum _ScaffoldSlot { enum _ScaffoldSlot {
...@@ -192,7 +208,7 @@ class Scaffold extends StatefulWidget { ...@@ -192,7 +208,7 @@ class Scaffold extends StatefulWidget {
this.appBarHeight this.appBarHeight
}) : super(key: key) { }) : super(key: key) {
assert((appBarBehavior == AppBarBehavior.scroll) ? scrollableKey != null : true); assert((appBarBehavior == AppBarBehavior.scroll) ? scrollableKey != null : true);
assert((appBarBehavior == AppBarBehavior.scroll) ? appBarHeight != null && appBarHeight > kAppBarHeight : true); assert((appBarBehavior == AppBarBehavior.scroll) ? appBarHeight != null && appBarHeight > kToolBarHeight : true);
} }
final AppBar appBar; final AppBar appBar;
...@@ -430,36 +446,53 @@ class ScaffoldState extends State<Scaffold> { ...@@ -430,36 +446,53 @@ class ScaffoldState extends State<Scaffold> {
)); ));
} }
Widget _buildAnchoredAppBar(double toolBarHeight, EdgeInsets toolBarPadding) {
// Drive _appBarController to the point where the flexible space has disappeared.
_appBarController.value = (appBarHeight - toolBarHeight) / appBarHeight;
return new SizedBox(
height: toolBarHeight,
child: _getModifiedAppBar(padding: toolBarPadding)
);
}
Widget _buildScrollableAppBar(BuildContext context) { Widget _buildScrollableAppBar(BuildContext context) {
final EdgeInsets appBarPadding = MediaQuery.of(context)?.padding ?? EdgeInsets.zero; final EdgeInsets toolBarPadding = MediaQuery.of(context)?.padding ?? EdgeInsets.zero;
final double appBarHeight = kAppBarHeight + appBarPadding.top; final double toolBarHeight = kToolBarHeight + toolBarPadding.top;
Widget appBar; Widget appBar;
if (_scrollOffset <= appBarHeight && _scrollOffset >= appBarHeight - appBarHeight) { if (_scrollOffset <= appBarHeight && _scrollOffset >= appBarHeight - toolBarHeight) {
// scrolled to the top, only the app bar is (partially) visible // scrolled to the top, only the toolbar is (partially) visible.
final double height = math.max(_floatingAppBarHeight, appBarHeight - _scrollOffset); if (config.appBarBehavior == AppBarBehavior.under) {
final double opacity = _appBarOpacity(1.0 - ((appBarHeight - height) / appBarHeight)); appBar = _buildAnchoredAppBar(toolBarHeight, toolBarPadding);
_appBarController.value = (appBarHeight - height) / appBarHeight; } else {
appBar = new SizedBox( final double height = math.max(_floatingAppBarHeight, appBarHeight - _scrollOffset);
height: height, final double opacity = _appBarOpacity(1.0 - ((toolBarHeight - height) / toolBarHeight));
child: _getModifiedAppBar(padding: appBarPadding, foregroundOpacity: opacity) _appBarController.value = (appBarHeight - height) / appBarHeight;
); appBar = new SizedBox(
height: height,
child: _getModifiedAppBar(padding: toolBarPadding, foregroundOpacity: opacity)
);
}
} else if (_scrollOffset > appBarHeight) { } else if (_scrollOffset > appBarHeight) {
// scrolled down, show the "floating" app bar // scrolled past the entire app bar, maybe show the "floating" toolbar.
_floatingAppBarHeight = (_floatingAppBarHeight + _scrollOffsetDelta).clamp(0.0, appBarHeight); if (config.appBarBehavior == AppBarBehavior.under) {
final double appBarOpacity = _appBarOpacity(_floatingAppBarHeight / appBarHeight); appBar = _buildAnchoredAppBar(toolBarHeight, toolBarPadding);
_appBarController.value = (appBarHeight - _floatingAppBarHeight) / appBarHeight; } else {
appBar = new SizedBox( _floatingAppBarHeight = (_floatingAppBarHeight + _scrollOffsetDelta).clamp(0.0, toolBarHeight);
height: _floatingAppBarHeight, final double toolBarOpacity = _appBarOpacity(_floatingAppBarHeight / toolBarHeight);
child: _getModifiedAppBar(padding: appBarPadding, foregroundOpacity: appBarOpacity) _appBarController.value = (appBarHeight - _floatingAppBarHeight) / appBarHeight;
); appBar = new SizedBox(
height: _floatingAppBarHeight,
child: _getModifiedAppBar(padding: toolBarPadding, foregroundOpacity: toolBarOpacity)
);
}
} else { } else {
// _scrollOffset < appBarHeight - appBarHeight, scrolled to the top, flexible space is visible // _scrollOffset < appBarHeight - appBarHeight, scrolled to the top, flexible space is visible
final double height = appBarHeight - _scrollOffset.clamp(0.0, appBarHeight); final double height = appBarHeight - _scrollOffset.clamp(0.0, appBarHeight);
_appBarController.value = (appBarHeight - height) / appBarHeight; _appBarController.value = (appBarHeight - height) / appBarHeight;
appBar = new SizedBox( appBar = new SizedBox(
height: height, height: height,
child: _getModifiedAppBar(padding: appBarPadding, elevation: 0) child: _getModifiedAppBar(padding: toolBarPadding, elevation: 0)
); );
_floatingAppBarHeight = 0.0; _floatingAppBarHeight = 0.0;
} }
...@@ -468,10 +501,10 @@ class ScaffoldState extends State<Scaffold> { ...@@ -468,10 +501,10 @@ class ScaffoldState extends State<Scaffold> {
} }
Widget build(BuildContext context) { Widget build(BuildContext context) {
EdgeInsets padding = MediaQuery.of(context)?.padding ?? EdgeInsets.zero; final EdgeInsets padding = MediaQuery.of(context)?.padding ?? EdgeInsets.zero;
if (_snackBars.length > 0) { if (_snackBars.length > 0) {
ModalRoute<dynamic> route = ModalRoute.of(context); final ModalRoute<dynamic> route = ModalRoute.of(context);
if (route == null || route.isCurrent) { if (route == null || route.isCurrent) {
if (_snackBarController.isCompleted && _snackBarTimer == null) if (_snackBarController.isCompleted && _snackBarTimer == null)
_snackBarTimer = new Timer(_snackBars.first._widget.duration, _hideSnackBar); _snackBarTimer = new Timer(_snackBars.first._widget.duration, _hideSnackBar);
...@@ -484,7 +517,7 @@ class ScaffoldState extends State<Scaffold> { ...@@ -484,7 +517,7 @@ class ScaffoldState extends State<Scaffold> {
final List<LayoutId> children = new List<LayoutId>(); final List<LayoutId> children = new List<LayoutId>();
_addIfNonNull(children, config.body, _ScaffoldSlot.body); _addIfNonNull(children, config.body, _ScaffoldSlot.body);
if (config.appBarBehavior == AppBarBehavior.anchor) { if (config.appBarBehavior == AppBarBehavior.anchor) {
Widget appBar = new ConstrainedBox( final Widget appBar = new ConstrainedBox(
child: _getModifiedAppBar(padding: padding), child: _getModifiedAppBar(padding: padding),
constraints: new BoxConstraints(maxHeight: config.appBarHeight ?? kExtendedAppBarHeight + padding.top) constraints: new BoxConstraints(maxHeight: config.appBarHeight ?? kExtendedAppBarHeight + padding.top)
); );
...@@ -494,7 +527,7 @@ class ScaffoldState extends State<Scaffold> { ...@@ -494,7 +527,7 @@ class ScaffoldState extends State<Scaffold> {
if (_currentBottomSheet != null || if (_currentBottomSheet != null ||
(_dismissedBottomSheets != null && _dismissedBottomSheets.isNotEmpty)) { (_dismissedBottomSheets != null && _dismissedBottomSheets.isNotEmpty)) {
List<Widget> bottomSheets = <Widget>[]; final List<Widget> bottomSheets = <Widget>[];
if (_dismissedBottomSheets != null && _dismissedBottomSheets.isNotEmpty) if (_dismissedBottomSheets != null && _dismissedBottomSheets.isNotEmpty)
bottomSheets.addAll(_dismissedBottomSheets); bottomSheets.addAll(_dismissedBottomSheets);
if (_currentBottomSheet != null) if (_currentBottomSheet != null)
...@@ -510,7 +543,7 @@ class ScaffoldState extends State<Scaffold> { ...@@ -510,7 +543,7 @@ class ScaffoldState extends State<Scaffold> {
_addIfNonNull(children, _snackBars.first._widget, _ScaffoldSlot.snackBar); _addIfNonNull(children, _snackBars.first._widget, _ScaffoldSlot.snackBar);
if (config.floatingActionButton != null) { if (config.floatingActionButton != null) {
Widget fab = new _FloatingActionButtonTransition( final Widget fab = new _FloatingActionButtonTransition(
key: new ValueKey<Key>(config.floatingActionButton.key), key: new ValueKey<Key>(config.floatingActionButton.key),
child: config.floatingActionButton child: config.floatingActionButton
); );
...@@ -529,8 +562,7 @@ class ScaffoldState extends State<Scaffold> { ...@@ -529,8 +562,7 @@ class ScaffoldState extends State<Scaffold> {
Widget application; Widget application;
if (config.appBarBehavior == AppBarBehavior.scroll) { if (config.appBarBehavior != AppBarBehavior.anchor) {
double overScroll = _scrollOffset.clamp(double.NEGATIVE_INFINITY, 0.0);
application = new NotificationListener<ScrollNotification>( application = new NotificationListener<ScrollNotification>(
onNotification: _handleScrollNotification, onNotification: _handleScrollNotification,
child: new Stack( child: new Stack(
...@@ -542,7 +574,7 @@ class ScaffoldState extends State<Scaffold> { ...@@ -542,7 +574,7 @@ class ScaffoldState extends State<Scaffold> {
) )
), ),
new Positioned( new Positioned(
top: -overScroll, top: 0.0,
left: 0.0, left: 0.0,
right: 0.0, right: 0.0,
child: _buildScrollableAppBar(context) child: _buildScrollableAppBar(context)
......
...@@ -45,7 +45,7 @@ class ScrollableList extends Scrollable { ...@@ -45,7 +45,7 @@ class ScrollableList extends Scrollable {
} }
class _ScrollableListState extends ScrollableState<ScrollableList> { class _ScrollableListState extends ScrollableState<ScrollableList> {
ScrollBehavior<double, double> createScrollBehavior() => new OverscrollBehavior(); ScrollBehavior<double, double> createScrollBehavior() => new OverscrollWhenScrollableBehavior();
ExtentScrollBehavior get scrollBehavior => super.scrollBehavior; ExtentScrollBehavior get scrollBehavior => super.scrollBehavior;
void _handleExtentsChanged(double contentExtent, double containerExtent) { void _handleExtentsChanged(double contentExtent, double containerExtent) {
......
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