Commit 3480d8db authored by Hans Muller's avatar Hans Muller

Merge pull request #2597 from HansMuller/toolbar

Added AppBarBehavior.under, etc
parents bedd8e91 23d7a23e
......@@ -72,8 +72,10 @@ class FlexibleSpaceDemo extends StatefulWidget {
}
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 @@ class FlexibleSpaceDemoState extends State<FlexibleSpaceDemo> {
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) {
......
......@@ -37,7 +37,7 @@ class GallerySection extends StatelessWidget {
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))
......
......@@ -117,7 +117,7 @@ class AppBar extends StatelessWidget {
// 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(
......
......@@ -9,7 +9,7 @@ import 'package:flutter/widgets.dart';
// 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;
......
......@@ -27,7 +27,7 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
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 @@ class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
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,
......
......@@ -20,9 +20,25 @@ import 'snack_bar.dart';
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 @@ class Scaffold extends StatefulWidget {
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 @@ 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) {
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 @@ class ScaffoldState extends State<Scaffold> {
}
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 @@ class ScaffoldState extends State<Scaffold> {
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 @@ class ScaffoldState extends State<Scaffold> {
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 @@ class ScaffoldState extends State<Scaffold> {
_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 @@ class ScaffoldState extends State<Scaffold> {
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 @@ class ScaffoldState extends State<Scaffold> {
)
),
new Positioned(
top: -overScroll,
top: 0.0,
left: 0.0,
right: 0.0,
child: _buildScrollableAppBar(context)
......
......@@ -45,7 +45,7 @@ class ScrollableList extends Scrollable {
}
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) {
......
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