mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
AppBar bottom widget, not necessarily a TabBar (#4631)
This commit is contained in:
parent
ef6a4faa8d
commit
c707b53f01
@ -119,7 +119,7 @@ class ColorsDemo extends StatelessWidget {
|
||||
appBar: new AppBar(
|
||||
elevation: 0,
|
||||
title: new Text('Colors'),
|
||||
tabBar: new TabBar<ColorSwatch>(
|
||||
bottom: new TabBar<ColorSwatch>(
|
||||
isScrollable: true,
|
||||
labels: new Map<ColorSwatch, TabLabel>.fromIterable(colorSwatches, value: (ColorSwatch swatch) {
|
||||
return new TabLabel(text: swatch.name);
|
||||
|
@ -71,7 +71,7 @@ class ScrollableTabsDemoState extends State<ScrollableTabsDemo> {
|
||||
]
|
||||
)
|
||||
],
|
||||
tabBar: new TabBar<IconData>(
|
||||
bottom: new TabBar<IconData>(
|
||||
isScrollable: true,
|
||||
labels: new Map<IconData, TabLabel>.fromIterable(
|
||||
icons,
|
||||
|
@ -50,7 +50,7 @@ class TabsDemoState extends State<TabsDemo> {
|
||||
appBarBehavior: AppBarBehavior.under,
|
||||
appBar: new AppBar(
|
||||
title: new Text('Tabs and scrolling'),
|
||||
tabBar: new TabBar<_Page>(
|
||||
bottom: new TabBar<_Page>(
|
||||
labels: new Map<_Page, TabLabel>.fromIterable(_pages, value: (_Page page) {
|
||||
return new TabLabel(text: page.label);
|
||||
})
|
||||
|
@ -100,7 +100,7 @@ class _TabsFabDemoState extends State<TabsFabDemo> {
|
||||
key: scaffoldKey,
|
||||
appBar: new AppBar(
|
||||
title: new Text('FAB per tab'),
|
||||
tabBar: new TabBar<_Page>(
|
||||
bottom: new TabBar<_Page>(
|
||||
labels: new Map<_Page, TabLabel>.fromIterable(pages, value: (_Page page) => page.tabLabel)
|
||||
)
|
||||
),
|
||||
|
@ -16,15 +16,16 @@ class TwoLevelListDemo extends StatelessWidget {
|
||||
children: <Widget>[
|
||||
new TwoLevelListItem(title: new Text('Top')),
|
||||
new TwoLevelSublist(
|
||||
title: new Text('Sublist'),
|
||||
children: <Widget>[
|
||||
new TwoLevelListItem(title: new Text('One')),
|
||||
new TwoLevelListItem(title: new Text('Two')),
|
||||
new TwoLevelListItem(title: new Text('Free')),
|
||||
new TwoLevelListItem(title: new Text('Four'))
|
||||
]
|
||||
),
|
||||
new TwoLevelListItem(title: new Text('Bottom'))
|
||||
title: new Text('Sublist'),
|
||||
children: <Widget>[
|
||||
new TwoLevelListItem(title: new Text('One')),
|
||||
new TwoLevelListItem(title: new Text('Two')),
|
||||
// https://en.wikipedia.org/wiki/Free_Four
|
||||
new TwoLevelListItem(title: new Text('Free')),
|
||||
new TwoLevelListItem(title: new Text('Four'))
|
||||
]
|
||||
),
|
||||
new TwoLevelListItem(title: new Text('Bottom'))
|
||||
]
|
||||
)
|
||||
);
|
||||
|
@ -73,7 +73,7 @@ class TabbedComponentDemoScaffold extends StatelessWidget {
|
||||
child: new Scaffold(
|
||||
appBar: new AppBar(
|
||||
title: new Text(title),
|
||||
tabBar: new TabBar<ComponentDemoTabData>(
|
||||
bottom: new TabBar<ComponentDemoTabData>(
|
||||
isScrollable: true,
|
||||
labels: ComponentDemoTabData.buildTabLabels(demos)
|
||||
)
|
||||
|
@ -241,7 +241,7 @@ class StockHomeState extends State<StockHome> {
|
||||
]
|
||||
)
|
||||
],
|
||||
tabBar: new TabBar<StockHomeTab>(
|
||||
bottom: new TabBar<StockHomeTab>(
|
||||
labels: <StockHomeTab, TabLabel>{
|
||||
StockHomeTab.market: new TabLabel(text: StockStrings.of(context).market()),
|
||||
StockHomeTab.portfolio: new TabLabel(text: StockStrings.of(context).portfolio())
|
||||
|
@ -14,6 +14,14 @@ import 'tabs.dart';
|
||||
import 'theme.dart';
|
||||
import 'typography.dart';
|
||||
|
||||
/// A widget that can appear at the bottom of an [AppBar]. The [Scaffold] uses
|
||||
/// the bottom widget's [bottomHeight] to handle layout for
|
||||
/// [AppBarBehavior.scroll] and [AppBarBehavior.under].
|
||||
abstract class AppBarBottomWidget extends Widget {
|
||||
/// Defines the height of the app bar's optional bottom widget.
|
||||
double get bottomHeight;
|
||||
}
|
||||
|
||||
// TODO(eseidel) Toolbar needs to change size based on orientation:
|
||||
// http://www.google.com/design/spec/layout/structure.html#structure-app-bar
|
||||
// Mobile Landscape: 48dp
|
||||
@ -30,10 +38,13 @@ import 'typography.dart';
|
||||
/// App bars are most commonly used in the [Scaffold.appBar] property, which
|
||||
/// places the app bar at the top of the app.
|
||||
///
|
||||
/// The AppBar displays the toolbar widgets, [leading], [title],
|
||||
/// and [actions], above the [tabBar] (if any). If a [flexibleSpace] widget is
|
||||
/// specified then it is stacked behind the toolbar and tabbar. The [Scaffold]
|
||||
/// typically creates the appbar with an initial height equal to [expandedHeight].
|
||||
/// The AppBar displays the toolbar widgets, [leading], [title], and
|
||||
/// [actions], above the [bottom] (if any). If a [flexibleSpace] widget is
|
||||
/// specified then it is stacked behind the toolbar and the bottom widget.
|
||||
/// The [Scaffold] typically creates the appbar with an initial height equal to
|
||||
/// [expandedHeight]. If the [Scaffold.appBarBehavior] is set then the
|
||||
/// AppBar's [collapsedHeight] and [bottomHeight] define how small the app bar
|
||||
/// will become when the application is scrolled.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
@ -53,18 +64,16 @@ class AppBar extends StatelessWidget {
|
||||
this.title,
|
||||
this.actions,
|
||||
this.flexibleSpace,
|
||||
this.tabBar,
|
||||
this.bottom,
|
||||
this.elevation: 4,
|
||||
this.backgroundColor,
|
||||
this.brightness,
|
||||
this.textTheme,
|
||||
this.padding: EdgeInsets.zero,
|
||||
double expandedHeight,
|
||||
double collapsedHeight,
|
||||
double minimumHeight
|
||||
double collapsedHeight
|
||||
}) : _expandedHeight = expandedHeight,
|
||||
_collapsedHeight = collapsedHeight,
|
||||
_minimumHeight = minimumHeight,
|
||||
super(key: key);
|
||||
|
||||
/// A widget to display before the [title].
|
||||
@ -77,7 +86,7 @@ class AppBar extends StatelessWidget {
|
||||
/// field with an [IconButton] that calls [Navigator.pop].
|
||||
final Widget leading;
|
||||
|
||||
/// The primary widget displayed in the app bar.
|
||||
/// The primary widget displayed in the appbar.
|
||||
///
|
||||
/// Typically a [Text] widget containing a description of the current contents
|
||||
/// of the app.
|
||||
@ -97,8 +106,10 @@ class AppBar extends StatelessWidget {
|
||||
/// Typically a [FlexibleSpaceBar]. See [FlexibleSpaceBar] for details.
|
||||
final Widget flexibleSpace;
|
||||
|
||||
/// A horizontal bar of tabs to display at the bottom of the app bar.
|
||||
final TabBar<dynamic> tabBar;
|
||||
/// This widget appears across the bottom of the appbar.
|
||||
///
|
||||
/// Typically a [TabBar].
|
||||
final AppBarBottomWidget bottom;
|
||||
|
||||
/// The z-coordinate at which to place this app bar.
|
||||
///
|
||||
@ -129,7 +140,6 @@ class AppBar extends StatelessWidget {
|
||||
|
||||
final double _expandedHeight;
|
||||
final double _collapsedHeight;
|
||||
final double _minimumHeight;
|
||||
|
||||
/// Creates a copy of this app bar but with the given fields replaced with the new values.
|
||||
AppBar copyWith({
|
||||
@ -138,6 +148,7 @@ class AppBar extends StatelessWidget {
|
||||
Widget title,
|
||||
List<Widget> actions,
|
||||
Widget flexibleSpace,
|
||||
AppBarBottomWidget bottom,
|
||||
int elevation,
|
||||
Color backgroundColor,
|
||||
Brightness brightness,
|
||||
@ -152,7 +163,7 @@ class AppBar extends StatelessWidget {
|
||||
title: title ?? this.title,
|
||||
actions: actions ?? this.actions,
|
||||
flexibleSpace: flexibleSpace ?? this.flexibleSpace,
|
||||
tabBar: tabBar ?? this.tabBar,
|
||||
bottom: bottom ?? this.bottom,
|
||||
elevation: elevation ?? this.elevation,
|
||||
backgroundColor: backgroundColor ?? this.backgroundColor,
|
||||
brightness: brightness ?? this.brightness,
|
||||
@ -163,31 +174,31 @@ class AppBar extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
double get _tabBarHeight => tabBar == null ? null : tabBar.minimumHeight;
|
||||
|
||||
double get _toolBarHeight => kToolBarHeight;
|
||||
|
||||
/// By default, the height of the toolbar and the tabbar (if any).
|
||||
/// The [Scaffold] gives its appbar this height initially. If a
|
||||
/// The height of the bottom widget. The [Scaffold] uses this value to control
|
||||
/// the size of the app bar when its appBarBehavior is [AppBarBehavior.scroll]
|
||||
/// or [AppBarBehavior.under].
|
||||
double get bottomHeight => bottom?.bottomHeight ?? 0.0;
|
||||
|
||||
/// By default, the total height of the toolbar and the bottom widget (if any).
|
||||
/// The [Scaffold] gives its app bar this height initially. If a
|
||||
/// [flexibleSpace] widget is specified this height should be big
|
||||
/// enough to accommodate whatever that widget contains.
|
||||
double get expandedHeight => _expandedHeight ?? (_toolBarHeight + (_tabBarHeight ?? 0.0));
|
||||
double get expandedHeight => _expandedHeight ?? (_toolBarHeight + bottomHeight);
|
||||
|
||||
/// By default, the height of the toolbar and the tabbar (if any).
|
||||
/// By default, the height of the toolbar and the bottom widget (if any).
|
||||
/// If the height of the app bar is constrained to be less than this value
|
||||
/// the toolbar and tabbar are scrolled upwards, out of view.
|
||||
double get collapsedHeight => _collapsedHeight ?? (_toolBarHeight + (_tabBarHeight ?? 0.0));
|
||||
|
||||
double get minimumHeight => _minimumHeight ?? _tabBarHeight ?? _toolBarHeight;
|
||||
/// then the toolbar and bottom widget are scrolled upwards, out of view.
|
||||
double get collapsedHeight => _collapsedHeight ?? (_toolBarHeight + bottomHeight);
|
||||
|
||||
// Defines the opacity of the toolbar's text and icons.
|
||||
double _toolBarOpacity(double appBarHeight, double statusBarHeight) {
|
||||
return ((appBarHeight - (_tabBarHeight ?? 0.0) - statusBarHeight) / _toolBarHeight).clamp(0.0, 1.0);
|
||||
return ((appBarHeight - bottomHeight - statusBarHeight) / _toolBarHeight).clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
double _tabBarOpacity(double appBarHeight, double statusBarHeight) {
|
||||
final double tabBarHeight = _tabBarHeight ?? 0.0;
|
||||
return ((appBarHeight - statusBarHeight) / tabBarHeight).clamp(0.0, 1.0);
|
||||
double _bottomOpacity(double appBarHeight, double statusBarHeight) {
|
||||
return ((appBarHeight - statusBarHeight) / bottomHeight).clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
Widget _buildForSize(BuildContext context, BoxConstraints constraints) {
|
||||
@ -254,15 +265,15 @@ class AppBar extends StatelessWidget {
|
||||
)
|
||||
);
|
||||
|
||||
final double tabBarOpacity = _tabBarOpacity(size.height, statusBarHeight);
|
||||
if (tabBar != null) {
|
||||
final double bottomOpacity = _bottomOpacity(size.height, statusBarHeight);
|
||||
if (bottom != null) {
|
||||
appBar = new Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
children: <Widget>[
|
||||
appBar,
|
||||
tabBarOpacity == 1.0 ? tabBar : new Opacity(
|
||||
child: tabBar,
|
||||
opacity: const Interval(0.25, 1.0, curve: Curves.ease).transform(tabBarOpacity)
|
||||
bottomOpacity == 1.0 ? bottom : new Opacity(
|
||||
child: bottom,
|
||||
opacity: const Interval(0.25, 1.0, curve: Curves.ease).transform(bottomOpacity)
|
||||
)
|
||||
]
|
||||
);
|
||||
|
@ -20,24 +20,27 @@ const double _kFloatingActionButtonMargin = 16.0; // TODO(hmuller): should be de
|
||||
const Duration _kFloatingActionButtonSegue = const Duration(milliseconds: 200);
|
||||
final Tween<double> _kFloatingActionButtonTurnTween = new Tween<double>(begin: -0.125, end: 0.0);
|
||||
|
||||
/// 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.
|
||||
/// The Scaffold's appbar is the toolbar, bottom, and the "flexible space"
|
||||
/// that's stacked behind them. The Scaffold's appBarBehavior defines how
|
||||
/// its layout responds to scrolling the application's body.
|
||||
enum AppBarBehavior {
|
||||
/// The tool bar's layout does not respond to scrolling.
|
||||
/// The app bar's layout does not respond to scrolling.
|
||||
anchor,
|
||||
|
||||
/// The tool bar's appearance and layout depend on the scrollOffset of the
|
||||
/// The app 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.
|
||||
/// and then the app bar fades out and scrolls off the top of the screen.
|
||||
/// Scrolling upwards always causes the app bar's bottom widget to reappear
|
||||
/// if the bottom widget isn't null, otherwise the app bar's toolbar reappears.
|
||||
scroll,
|
||||
|
||||
/// The tool bar's appearance and layout depend on the scrollOffset of the
|
||||
/// The app 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.
|
||||
/// If the bottom widget isn't null the app bar shrinks to the bottom widget's
|
||||
/// [AppBarBottomWidget.bottomHeight], otherwise the app bar shrinks to its
|
||||
/// [AppBar.collapsedHeight].
|
||||
under,
|
||||
}
|
||||
|
||||
@ -612,13 +615,14 @@ class ScaffoldState extends State<Scaffold> {
|
||||
Widget _buildScrollableAppBar(BuildContext context, EdgeInsets padding) {
|
||||
final double expandedHeight = (config.appBar?.expandedHeight ?? 0.0) + padding.top;
|
||||
final double collapsedHeight = (config.appBar?.collapsedHeight ?? 0.0) + padding.top;
|
||||
final double minimumHeight = (config.appBar?.minimumHeight ?? 0.0) + padding.top;
|
||||
final double bottomHeight = config.appBar?.bottomHeight + padding.top;
|
||||
final double underHeight = config.appBar.bottom != null ? bottomHeight : collapsedHeight;
|
||||
Widget appBar;
|
||||
|
||||
if (_scrollOffset <= expandedHeight && _scrollOffset >= expandedHeight - minimumHeight) {
|
||||
if (_scrollOffset <= expandedHeight && _scrollOffset >= expandedHeight - underHeight) {
|
||||
// scrolled to the top, flexible space collapsed, only the toolbar and tabbar are (partially) visible.
|
||||
if (config.appBarBehavior == AppBarBehavior.under) {
|
||||
appBar = _buildAnchoredAppBar(expandedHeight, minimumHeight, padding);
|
||||
appBar = _buildAnchoredAppBar(expandedHeight, underHeight, padding);
|
||||
} else {
|
||||
final double height = math.max(_floatingAppBarHeight, expandedHeight - _scrollOffset);
|
||||
_appBarController.value = (expandedHeight - height) / expandedHeight;
|
||||
@ -630,7 +634,7 @@ class ScaffoldState extends State<Scaffold> {
|
||||
} else if (_scrollOffset > expandedHeight) {
|
||||
// scrolled past the entire app bar, maybe show the "floating" toolbar.
|
||||
if (config.appBarBehavior == AppBarBehavior.under) {
|
||||
appBar = _buildAnchoredAppBar(expandedHeight, minimumHeight, padding);
|
||||
appBar = _buildAnchoredAppBar(expandedHeight, underHeight, padding);
|
||||
} else {
|
||||
_floatingAppBarHeight = (_floatingAppBarHeight + _scrollOffsetDelta).clamp(0.0, collapsedHeight);
|
||||
_appBarController.value = (expandedHeight - _floatingAppBarHeight) / expandedHeight;
|
||||
|
@ -10,6 +10,7 @@ import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'app_bar.dart';
|
||||
import 'colors.dart';
|
||||
import 'debug.dart';
|
||||
import 'icon.dart';
|
||||
@ -643,7 +644,7 @@ class TabBarSelectionState<T> extends State<TabBarSelection<T>> {
|
||||
/// * [TabBarView]
|
||||
/// * [AppBar.tabBar]
|
||||
/// * <https://www.google.com/design/spec/components/tabs.html>
|
||||
class TabBar<T> extends Scrollable {
|
||||
class TabBar<T> extends Scrollable implements AppBarBottomWidget {
|
||||
TabBar({
|
||||
Key key,
|
||||
this.labels,
|
||||
@ -671,7 +672,9 @@ class TabBar<T> extends Scrollable {
|
||||
/// the color of the theme's body2 text color is used.
|
||||
final Color labelColor;
|
||||
|
||||
double get minimumHeight {
|
||||
/// The height of the tab labels and indicator.
|
||||
@override
|
||||
double get bottomHeight {
|
||||
for (TabLabel label in labels.values) {
|
||||
if (label.text != null && (label.icon != null || label.iconBuilder != null))
|
||||
return _kTextAndIconTabHeight + _kTabIndicatorHeight;
|
||||
|
Loading…
Reference in New Issue
Block a user