Added AppBarBehavior.under, etc
diff --git a/examples/material_gallery/lib/demo/flexible_space_demo.dart b/examples/material_gallery/lib/demo/flexible_space_demo.dart index 0282dbe..ec7eab3 100644 --- a/examples/material_gallery/lib/demo/flexible_space_demo.dart +++ b/examples/material_gallery/lib/demo/flexible_space_demo.dart
@@ -72,8 +72,10 @@ } class FlexibleSpaceDemoState extends State<FlexibleSpaceDemo> { + final GlobalKey<ScaffoldState> scaffoldKey = new GlobalKey<ScaffoldState>(); final double appBarHeight = 256.0; final Key scrollableKey = new UniqueKey(); + AppBarBehavior _appBarBehavior = AppBarBehavior.scroll; Widget build(BuildContext context) { return new Theme( @@ -82,18 +84,37 @@ primarySwatch: Colors.indigo ), child: new Scaffold( + key: scaffoldKey, appBarHeight: appBarHeight, scrollableKey: scrollableKey, - appBarBehavior: AppBarBehavior.scroll, + appBarBehavior: _appBarBehavior, appBar: new AppBar( actions: <Widget>[ new IconButton( icon: Icons.create, - tooltip: 'Search' + tooltip: 'Search', + onPressed: () { + scaffoldKey.currentState.showSnackBar(new SnackBar( + content: new Text('Not supported.') + )); + } ), - new IconButton( - icon: Icons.more_vert, - tooltip: 'Show menu' + new PopupMenuButton<AppBarBehavior>( + onSelected: (AppBarBehavior value) { + 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) {
diff --git a/examples/material_gallery/lib/gallery/section.dart b/examples/material_gallery/lib/gallery/section.dart index c1060f1..2670c2d 100644 --- a/examples/material_gallery/lib/gallery/section.dart +++ b/examples/material_gallery/lib/gallery/section.dart
@@ -37,7 +37,7 @@ data: theme, child: new Scaffold( appBarHeight: appBarHeight, - appBarBehavior: AppBarBehavior.scroll, + appBarBehavior: AppBarBehavior.under, scrollableKey: scrollableKey, appBar: new AppBar( flexibleSpace: (BuildContext context) => new FlexibleSpaceBar(title: new Text(title))
diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index d478d6e..596bcd8 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart
@@ -117,7 +117,7 @@ // 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. - final double toolBarHeight = kAppBarHeight + combinedPadding.top + combinedPadding.bottom; + final double toolBarHeight = kToolBarHeight + combinedPadding.top + combinedPadding.bottom; final Widget toolBar = new ConstrainedBox( constraints: new BoxConstraints(maxHeight: toolBarHeight), child: new Padding(
diff --git a/packages/flutter/lib/src/material/constants.dart b/packages/flutter/lib/src/material/constants.dart index 93f37f0..f56f506 100644 --- a/packages/flutter/lib/src/material/constants.dart +++ b/packages/flutter/lib/src/material/constants.dart
@@ -9,7 +9,7 @@ // Mobile Landscape: 48dp // Mobile Portrait: 56dp // Tablet/Desktop: 64dp -const double kAppBarHeight = 56.0; +const double kToolBarHeight = 56.0; const double kExtendedAppBarHeight = 128.0; const double kTextTabBarHeight = 48.0;
diff --git a/packages/flutter/lib/src/material/flexible_space_bar.dart b/packages/flutter/lib/src/material/flexible_space_bar.dart index b9d4408..f61b63e 100644 --- a/packages/flutter/lib/src/material/flexible_space_bar.dart +++ b/packages/flutter/lib/src/material/flexible_space_bar.dart
@@ -27,7 +27,7 @@ final double appBarHeight = Scaffold.of(context).appBarHeight; final Animation<double> animation = Scaffold.of(context).appBarAnimation; 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>[]; // background image @@ -63,7 +63,7 @@ color: titleStyle.color.withAlpha(new Tween<double>(begin: 255.0, end: 0.0).evaluate(opacityCurve).toInt()) ); 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 CurvedAnimation scaleAndAlignCurve = new CurvedAnimation( parent: animation,
diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart index 8cb699d..954d3de 100644 --- a/packages/flutter/lib/src/material/scaffold.dart +++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -20,9 +20,25 @@ const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be device dependent 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 { + /// The tool bar's layout does not respond to scrolling. 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, + + /// 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 { @@ -192,7 +208,7 @@ this.appBarHeight }) : super(key: key) { 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; @@ -430,36 +446,53 @@ )); } + 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) { - final EdgeInsets appBarPadding = MediaQuery.of(context)?.padding ?? EdgeInsets.zero; - final double appBarHeight = kAppBarHeight + appBarPadding.top; + final EdgeInsets toolBarPadding = MediaQuery.of(context)?.padding ?? EdgeInsets.zero; + final double toolBarHeight = kToolBarHeight + toolBarPadding.top; Widget appBar; - if (_scrollOffset <= appBarHeight && _scrollOffset >= appBarHeight - appBarHeight) { - // scrolled to the top, only the app bar is (partially) visible - final double height = math.max(_floatingAppBarHeight, appBarHeight - _scrollOffset); - final double opacity = _appBarOpacity(1.0 - ((appBarHeight - height) / appBarHeight)); - _appBarController.value = (appBarHeight - height) / appBarHeight; - appBar = new SizedBox( - height: height, - child: _getModifiedAppBar(padding: appBarPadding, foregroundOpacity: opacity) - ); + if (_scrollOffset <= appBarHeight && _scrollOffset >= appBarHeight - toolBarHeight) { + // scrolled to the top, only the toolbar is (partially) visible. + if (config.appBarBehavior == AppBarBehavior.under) { + appBar = _buildAnchoredAppBar(toolBarHeight, toolBarPadding); + } else { + final double height = math.max(_floatingAppBarHeight, appBarHeight - _scrollOffset); + final double opacity = _appBarOpacity(1.0 - ((toolBarHeight - height) / toolBarHeight)); + _appBarController.value = (appBarHeight - height) / appBarHeight; + appBar = new SizedBox( + height: height, + child: _getModifiedAppBar(padding: toolBarPadding, foregroundOpacity: opacity) + ); + } } else if (_scrollOffset > appBarHeight) { - // scrolled down, show the "floating" app bar - _floatingAppBarHeight = (_floatingAppBarHeight + _scrollOffsetDelta).clamp(0.0, appBarHeight); - final double appBarOpacity = _appBarOpacity(_floatingAppBarHeight / appBarHeight); - _appBarController.value = (appBarHeight - _floatingAppBarHeight) / appBarHeight; - appBar = new SizedBox( - height: _floatingAppBarHeight, - child: _getModifiedAppBar(padding: appBarPadding, foregroundOpacity: appBarOpacity) - ); + // scrolled past the entire app bar, maybe show the "floating" toolbar. + if (config.appBarBehavior == AppBarBehavior.under) { + appBar = _buildAnchoredAppBar(toolBarHeight, toolBarPadding); + } else { + _floatingAppBarHeight = (_floatingAppBarHeight + _scrollOffsetDelta).clamp(0.0, toolBarHeight); + final double toolBarOpacity = _appBarOpacity(_floatingAppBarHeight / toolBarHeight); + _appBarController.value = (appBarHeight - _floatingAppBarHeight) / appBarHeight; + appBar = new SizedBox( + height: _floatingAppBarHeight, + child: _getModifiedAppBar(padding: toolBarPadding, foregroundOpacity: toolBarOpacity) + ); + } } else { // _scrollOffset < appBarHeight - appBarHeight, scrolled to the top, flexible space is visible final double height = appBarHeight - _scrollOffset.clamp(0.0, appBarHeight); _appBarController.value = (appBarHeight - height) / appBarHeight; appBar = new SizedBox( height: height, - child: _getModifiedAppBar(padding: appBarPadding, elevation: 0) + child: _getModifiedAppBar(padding: toolBarPadding, elevation: 0) ); _floatingAppBarHeight = 0.0; } @@ -468,10 +501,10 @@ } 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) { - ModalRoute<dynamic> route = ModalRoute.of(context); + final ModalRoute<dynamic> route = ModalRoute.of(context); if (route == null || route.isCurrent) { if (_snackBarController.isCompleted && _snackBarTimer == null) _snackBarTimer = new Timer(_snackBars.first._widget.duration, _hideSnackBar); @@ -484,7 +517,7 @@ final List<LayoutId> children = new List<LayoutId>(); _addIfNonNull(children, config.body, _ScaffoldSlot.body); if (config.appBarBehavior == AppBarBehavior.anchor) { - Widget appBar = new ConstrainedBox( + final Widget appBar = new ConstrainedBox( child: _getModifiedAppBar(padding: padding), constraints: new BoxConstraints(maxHeight: config.appBarHeight ?? kExtendedAppBarHeight + padding.top) ); @@ -494,7 +527,7 @@ if (_currentBottomSheet != null || (_dismissedBottomSheets != null && _dismissedBottomSheets.isNotEmpty)) { - List<Widget> bottomSheets = <Widget>[]; + final List<Widget> bottomSheets = <Widget>[]; if (_dismissedBottomSheets != null && _dismissedBottomSheets.isNotEmpty) bottomSheets.addAll(_dismissedBottomSheets); if (_currentBottomSheet != null) @@ -510,7 +543,7 @@ _addIfNonNull(children, _snackBars.first._widget, _ScaffoldSlot.snackBar); if (config.floatingActionButton != null) { - Widget fab = new _FloatingActionButtonTransition( + final Widget fab = new _FloatingActionButtonTransition( key: new ValueKey<Key>(config.floatingActionButton.key), child: config.floatingActionButton ); @@ -529,8 +562,7 @@ Widget application; - if (config.appBarBehavior == AppBarBehavior.scroll) { - double overScroll = _scrollOffset.clamp(double.NEGATIVE_INFINITY, 0.0); + if (config.appBarBehavior != AppBarBehavior.anchor) { application = new NotificationListener<ScrollNotification>( onNotification: _handleScrollNotification, child: new Stack( @@ -542,7 +574,7 @@ ) ), new Positioned( - top: -overScroll, + top: 0.0, left: 0.0, right: 0.0, child: _buildScrollableAppBar(context)
diff --git a/packages/flutter/lib/src/widgets/scrollable_list.dart b/packages/flutter/lib/src/widgets/scrollable_list.dart index 5641b1e..b8bfbdc 100644 --- a/packages/flutter/lib/src/widgets/scrollable_list.dart +++ b/packages/flutter/lib/src/widgets/scrollable_list.dart
@@ -45,7 +45,7 @@ } class _ScrollableListState extends ScrollableState<ScrollableList> { - ScrollBehavior<double, double> createScrollBehavior() => new OverscrollBehavior(); + ScrollBehavior<double, double> createScrollBehavior() => new OverscrollWhenScrollableBehavior(); ExtentScrollBehavior get scrollBehavior => super.scrollBehavior; void _handleExtentsChanged(double contentExtent, double containerExtent) {