app_bar.dart 10.2 KB
Newer Older
1 2 3 4
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'package:flutter/widgets.dart';
6

7
import 'constants.dart';
Adam Barth's avatar
Adam Barth committed
8 9
import 'icon_theme.dart';
import 'icon_theme_data.dart';
10
import 'material.dart';
11
import 'tabs.dart';
12
import 'theme.dart';
13
import 'typography.dart';
14

15 16 17 18 19 20
// 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
// Mobile Portrait: 56dp
// Tablet/Desktop: 64dp

21 22
/// A material design app bar.
///
23
/// An app bar consists of a toolbar and potentially other widgets, such as a
24 25 26 27
/// [TabBar] and a [FlexibleSpaceBar]. App bars typically expose one or more
/// common actions with [IconButtons]s which are optionally followed by a
/// [PopupMenuButton] for less common operations.
///
28 29
/// App bars are most commonly used in the [Scaffold.appBar] property, which
/// places the app bar at the top of the app.
30
///
31 32 33 34 35
/// 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].
///
36 37 38 39 40 41 42 43
/// See also:
///
///  * [Scaffold]
///  * [TabBar]
///  * [IconButton]
///  * [PopupMenuButton]
///  * [FlexibleSpaceBar]
///  * <https://www.google.com/design/spec/layout/structure.html#structure-toolbars>
44
class AppBar extends StatelessWidget {
45 46 47
  /// Creates a material design app bar.
  ///
  /// Typically used in the [Scaffold.appBar] property.
48
  AppBar({
49
    Key key,
50 51 52
    this.leading,
    this.title,
    this.actions,
53
    this.flexibleSpace,
54
    this.tabBar,
Hans Muller's avatar
Hans Muller committed
55
    this.elevation: 4,
Adam Barth's avatar
Adam Barth committed
56
    this.backgroundColor,
57
    this.textTheme,
58 59 60
    this.padding: EdgeInsets.zero,
    double expandedHeight,
    double collapsedHeight,
61
    double minimumHeight
62 63 64
  }) : _expandedHeight = expandedHeight,
       _collapsedHeight = collapsedHeight,
       _minimumHeight = minimumHeight,
65
       super(key: key);
66

67 68 69 70 71 72 73 74
  /// A widget to display before the [title].
  ///
  /// If this field is null and this app bar is used in a [Scaffold], the
  /// [Scaffold] will fill this field with an appropriate widget. For example,
  /// if the [Scaffold] also has a [Drawer], the [Scaffold] will fill this
  /// widget with an [IconButton] that opens the drawer. If there's no [Drawer]
  /// and the parent [Navigator] can go back, the [Scaffold] will fill this
  /// field with an [IconButton] that calls [Navigator.pop].
75
  final Widget leading;
76 77 78 79 80

  /// The primary widget displayed in the app bar.
  ///
  /// Typically a [Text] widget containing a description of the current contents
  /// of the app.
81
  final Widget title;
82

83
  /// Widgets to display after the title widget.
84 85 86 87
  ///
  /// Typically these widgets are [IconButton]s representing common operations.
  /// For less common operations, consider using a [PopupMenuButton] as the
  /// last action.
88
  final List<Widget> actions;
89

90 91 92
  /// This widget is stacked behind the toolbar and the tabbar and it is not
  /// inset by the specified [padding]. It's height will be the same as the
  /// the app bar's overall height.
93 94 95 96
  ///
  /// Typically a [FlexibleSpaceBar]. See [FlexibleSpaceBar] for details.
  final Widget flexibleSpace;

97
  /// A horizontal bar of tabs to display at the bottom of the app bar.
98
  final TabBar<dynamic> tabBar;
99 100

  /// The z-coordinate at which to place this app bar.
101 102
  ///
  /// The following elevations have defined shadows: 1, 2, 3, 4, 6, 8, 9, 12, 16, 24
Hans Muller's avatar
Hans Muller committed
103
  final int elevation;
104 105 106 107

  /// The color to use for the the app bar's material.
  ///
  /// Defaults to [ThemeData.primaryColor].
108
  final Color backgroundColor;
109 110 111 112

  /// The typographic style to use for text in the app bar.
  ///
  /// Defaults to [ThemeData.primaryTextTheme].
Adam Barth's avatar
Adam Barth committed
113
  final TextTheme textTheme;
114

115
  /// The amount of space by which to inset the contents of the app bar.
116 117
  /// The [Scaffold] increases [padding.top] by the height of the system
  /// status bar so that the toolbar appears below the status bar.
118
  final EdgeInsets padding;
119

120 121 122
  final double _expandedHeight;
  final double _collapsedHeight;
  final double _minimumHeight;
123

124
  /// Creates a copy of this app bar but with the given fields replaced with the new values.
125
  AppBar copyWith({
126
    Key key,
127 128 129
    Widget leading,
    Widget title,
    List<Widget> actions,
130
    Widget flexibleSpace,
131 132 133
    int elevation,
    Color backgroundColor,
    TextTheme textTheme,
134 135
    EdgeInsets padding,
    double expandedHeight,
136
    double collapsedHeight
137
  }) {
138
    return new AppBar(
139
      key: key ?? this.key,
140 141 142
      leading: leading ?? this.leading,
      title: title ?? this.title,
      actions: actions ?? this.actions,
143
      flexibleSpace: flexibleSpace ?? this.flexibleSpace,
144 145 146 147
      tabBar: tabBar ?? this.tabBar,
      elevation: elevation ?? this.elevation,
      backgroundColor: backgroundColor ?? this.backgroundColor,
      textTheme: textTheme ?? this.textTheme,
148 149
      padding: padding ?? this.padding,
      expandedHeight: expandedHeight ?? this._expandedHeight,
150
      collapsedHeight: collapsedHeight ?? this._collapsedHeight
151 152
    );
  }
153

154 155 156 157
  double get _tabBarHeight => tabBar == null ? null : tabBar.minimumHeight;

  double get _toolBarHeight => kToolBarHeight;

158 159 160 161
  /// By default, the height of the toolbar and the tabbar (if any).
  /// The [Scaffold] gives its appbar this height initially. If a
  /// [flexibleSpace] widget is specified this height should be big
  /// enough to accommodate whatever that widget contains.
162 163
  double get expandedHeight => _expandedHeight ?? (_toolBarHeight + (_tabBarHeight ?? 0.0));

164 165 166
  /// By default, the height of the toolbar and the tabbar (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.
167 168 169 170 171
  double get collapsedHeight => _collapsedHeight ?? (_toolBarHeight + (_tabBarHeight ?? 0.0));

  double get minimumHeight => _minimumHeight ?? _tabBarHeight ?? _toolBarHeight;

  // Defines the opacity of the toolbar's text and icons.
172 173
  double _toolBarOpacity(double appBarHeight, double statusBarHeight) {
    return ((appBarHeight - (_tabBarHeight ?? 0.0) - statusBarHeight) / _toolBarHeight).clamp(0.0, 1.0);
174 175
  }

176
  double _tabBarOpacity(double appBarHeight, double statusBarHeight) {
177
    final double tabBarHeight = _tabBarHeight ?? 0.0;
178
    return ((appBarHeight - statusBarHeight) / tabBarHeight).clamp(0.0, 1.0);
179 180
  }

181 182
  Widget _buildForSize(BuildContext context, Size size) {
    assert(size.height < double.INFINITY);
183
    final double statusBarHeight = MediaQuery.of(context).padding.top;
184 185 186 187 188
    final ThemeData theme = Theme.of(context);

    IconThemeData iconTheme = theme.primaryIconTheme;
    TextStyle centerStyle = textTheme?.title ?? theme.primaryTextTheme.title;
    TextStyle sideStyle = textTheme?.body1 ?? theme.primaryTextTheme.body1;
189

190
    final double toolBarOpacity = _toolBarOpacity(size.height, statusBarHeight);
191 192
    if (toolBarOpacity != 1.0) {
      final double opacity = const Interval(0.25, 1.0, curve: Curves.ease).transform(toolBarOpacity);
193
      if (centerStyle?.color != null)
194
        centerStyle = centerStyle.copyWith(color: centerStyle.color.withOpacity(opacity));
195
      if (sideStyle?.color != null)
196 197 198 199
        sideStyle = sideStyle.copyWith(color: sideStyle.color.withOpacity(opacity));

      if (iconTheme != null) {
        iconTheme = new IconThemeData(
200
          opacity: opacity * iconTheme.opacity,
201
          color: iconTheme.color
202 203 204 205 206
        );
      }
    }

    final List<Widget> toolBarRow = <Widget>[];
207 208 209 210 211 212 213 214 215
    if (leading != null) {
      toolBarRow.add(new Padding(
        padding: new EdgeInsets.only(right: 16.0),
        child: leading
      ));
    }
    toolBarRow.add(new Flexible(
      child: new Padding(
        padding: new EdgeInsets.only(left: 8.0),
216
        child: title != null ?
217
          new DefaultTextStyle(
218 219 220 221 222
            style: centerStyle,
            softWrap: false,
            overflow: TextOverflow.ellipsis,
            child: title
          ) : null
223
      )
224
    ));
225 226
    if (actions != null)
      toolBarRow.addAll(actions);
227

228 229 230 231
    Widget appBar = new SizedBox(
      height: kToolBarHeight,
      child: new IconTheme(
        data: iconTheme,
232
        child: new DefaultTextStyle(
233 234 235 236 237 238
          style: sideStyle,
          child: new Row(children: toolBarRow)
        )
      )
    );

239
    final double tabBarOpacity = _tabBarOpacity(size.height, statusBarHeight);
240 241
    if (tabBar != null) {
      appBar = new Column(
242
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
243 244 245 246 247 248 249 250 251 252
        children: <Widget>[
          appBar,
          tabBarOpacity == 1.0 ? tabBar : new Opacity(
            child: tabBar,
            opacity: const Interval(0.25, 1.0, curve: Curves.ease).transform(tabBarOpacity)
          )
        ]
      );
    }

253 254 255
    // The padding applies to the toolbar and tabbar, not the flexible space.
    // The incoming padding parameter's top value typically equals the height
    // of the status bar - so that the toolbar appears below the status bar.
256
    EdgeInsets combinedPadding = new EdgeInsets.symmetric(horizontal: 8.0);
257 258
    if (padding != null)
      combinedPadding += padding;
259 260 261 262
    appBar = new Padding(
      padding: combinedPadding,
      child: appBar
    );
263

264
    // If the appBar's height shrinks below collapsedHeight, it will be clipped and bottom
265 266
    // justified. This is so that the toolbar and the tabbar appear to move upwards as
    // the appBar's height is reduced below collapsedHeight.
267
    final double paddedCollapsedHeight = collapsedHeight + combinedPadding.top + combinedPadding.bottom;
268 269 270 271 272 273 274
    if (size.height < paddedCollapsedHeight) {
      appBar = new ClipRect(
        child: new OverflowBox(
          alignment: FractionalOffset.bottomLeft,
          minHeight: paddedCollapsedHeight,
          maxHeight: paddedCollapsedHeight,
          child: appBar
275
        )
276 277
      );
    }
278

279
    if (flexibleSpace != null) {
280 281
      appBar = new Stack(
        children: <Widget>[
282
          flexibleSpace,
283
          appBar
284 285 286 287
        ]
      );
    }

288 289
    appBar = new Material(
      color: backgroundColor ?? theme.primaryColor,
290
      elevation: elevation,
291
      child: appBar
292 293
    );

294
    return appBar;
295 296
  }

297 298
  @override
  Widget build(BuildContext context) => new LayoutBuilder(builder: _buildForSize);
299
}