123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269 |
- // Copyright 2014 The Flutter Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- import 'dart:math' as math;
- import 'package:flutter/foundation.dart';
- import 'package:flutter/material.dart' hide BackButtonIcon, kToolbarHeight;
- import 'package:flutter/rendering.dart';
- import 'package:flutter/services.dart';
- const double kToolbarHeight = 44;
- const double _kLeadingWidth =
- kToolbarHeight; // So the leading button is square.
- const double _kMaxTitleTextScaleFactor = 1.34;
- // Bottom justify the toolbarHeight child which may overflow the top.
- class _ToolbarContainerLayout extends SingleChildLayoutDelegate {
- const _ToolbarContainerLayout(this.toolbarHeight);
- final double toolbarHeight;
- @override
- BoxConstraints getConstraintsForChild(BoxConstraints constraints) {
- return constraints.tighten(height: toolbarHeight);
- }
- @override
- Size getSize(BoxConstraints constraints) {
- return Size(constraints.maxWidth, toolbarHeight);
- }
- @override
- Offset getPositionForChild(Size size, Size childSize) {
- return Offset(0.0, size.height - childSize.height);
- }
- @override
- bool shouldRelayout(_ToolbarContainerLayout oldDelegate) =>
- toolbarHeight != oldDelegate.toolbarHeight;
- }
- class WAppBar extends StatefulWidget implements PreferredSizeWidget {
- WAppBar({
- Key? key,
- this.leading,
- this.automaticallyImplyLeading = true,
- this.title,
- this.actions,
- this.flexibleSpace,
- this.bottom,
- this.elevation,
- this.shadowColor,
- this.shape,
- this.backgroundColor,
- this.brightness,
- this.iconTheme,
- this.actionsIconTheme,
- this.textTheme,
- this.primary = true,
- this.centerTitle = true,
- this.excludeHeaderSemantics = false,
- this.titleSpacing = NavigationToolbar.kMiddleSpacing,
- this.toolbarOpacity = 1.0,
- this.bottomOpacity = 1.0,
- this.toolbarHeight,
- this.leadingWidth,
- }) : assert(elevation == null || elevation >= 0.0),
- preferredSize = Size.fromHeight(toolbarHeight ??
- kToolbarHeight + (bottom?.preferredSize.height ?? 0.0)),
- super(key: key);
- final Widget? leading;
- final bool automaticallyImplyLeading;
- final Widget? title;
- final List<Widget>? actions;
- final Widget? flexibleSpace;
- final PreferredSizeWidget? bottom;
- final double? elevation;
- final Color? shadowColor;
- final ShapeBorder? shape;
- final Color? backgroundColor;
- final Brightness? brightness;
- /// The color, opacity, and size to use for app bar icons. Typically this
- /// is set along with [backgroundColor], [brightness], [textTheme].
- ///
- /// If this property is null, then [AppBarTheme.iconTheme] of
- /// [ThemeData.appBarTheme] is used. If that is also null, then
- /// [ThemeData.primaryIconTheme] is used.
- final IconThemeData? iconTheme;
- /// The color, opacity, and size to use for the icons that appear in the app
- /// bar's [actions]. This should only be used when the [actions] should be
- /// themed differently than the icon that appears in the app bar's [leading]
- /// widget.
- ///
- /// If this property is null, then [AppBarTheme.actionsIconTheme] of
- /// [ThemeData.appBarTheme] is used. If that is also null, then this falls
- /// back to [iconTheme].
- final IconThemeData? actionsIconTheme;
- /// The typographic styles to use for text in the app bar. Typically this is
- /// set along with [brightness] [backgroundColor], [iconTheme].
- ///
- /// If this property is null, then [AppBarTheme.textTheme] of
- /// [ThemeData.appBarTheme] is used. If that is also null, then
- /// [ThemeData.primaryTextTheme] is used.
- final TextTheme? textTheme;
- /// Whether this app bar is being displayed at the top of the screen.
- ///
- /// If true, the app bar's toolbar elements and [bottom] widget will be
- /// padded on top by the height of the system status bar. The layout
- /// of the [flexibleSpace] is not affected by the [primary] property.
- final bool primary;
- /// Whether the title should be centered.
- ///
- /// If this property is null, then [AppBarTheme.centerTitle] of
- /// [ThemeData.appBarTheme] is used. If that is also null, then value is
- /// adapted to the current [TargetPlatform].
- final bool? centerTitle;
- /// Whether the title should be wrapped with header [Semantics].
- ///
- /// Defaults to false.
- final bool excludeHeaderSemantics;
- /// The spacing around [title] content on the horizontal axis. This spacing is
- /// applied even if there is no [leading] content or [actions]. If you want
- /// [title] to take all the space available, set this value to 0.0.
- ///
- /// Defaults to [NavigationToolbar.kMiddleSpacing].
- final double titleSpacing;
- /// How opaque the toolbar part of the app bar is.
- ///
- /// A value of 1.0 is fully opaque, and a value of 0.0 is fully transparent.
- ///
- /// Typically, this value is not changed from its default value (1.0). It is
- /// used by [SliverWAppBar] to animate the opacity of the toolbar when the app
- /// bar is scrolled.
- final double toolbarOpacity;
- /// How opaque the bottom part of the app bar is.
- ///
- /// A value of 1.0 is fully opaque, and a value of 0.0 is fully transparent.
- ///
- /// Typically, this value is not changed from its default value (1.0). It is
- /// used by [SliverWAppBar] to animate the opacity of the toolbar when the app
- /// bar is scrolled.
- final double bottomOpacity;
- /// A size whose height is the sum of [toolbarHeight] and the [bottom] widget's
- /// preferred height.
- ///
- /// [Scaffold] uses this size to set its app bar's height.
- @override
- final Size preferredSize;
- /// Defines the height of the toolbar component of an [WAppBar].
- ///
- /// By default, the value of `toolbarHeight` is [kToolbarHeight].
- final double? toolbarHeight;
- /// Defines the width of [leading] widget.
- ///
- /// By default, the value of `leadingWidth` is 56.0.
- final double? leadingWidth;
- bool? _getEffectiveCenterTitle(ThemeData theme) {
- if (centerTitle != null) return centerTitle;
- if (theme.appBarTheme.centerTitle != null)
- return theme.appBarTheme.centerTitle;
- switch (theme.platform) {
- case TargetPlatform.android:
- case TargetPlatform.fuchsia:
- case TargetPlatform.linux:
- case TargetPlatform.windows:
- return false;
- case TargetPlatform.iOS:
- case TargetPlatform.macOS:
- return actions == null || actions!.length < 2;
- }
- }
- @override
- _WAppBarState createState() => _WAppBarState();
- }
- class _WAppBarState extends State<WAppBar> {
- static const double _defaultElevation = 4.0;
- static const Color _defaultShadowColor = Color(0xFF000000);
- void _handleDrawerButton() {
- Scaffold.of(context).openDrawer();
- }
- void _handleDrawerButtonEnd() {
- Scaffold.of(context).openEndDrawer();
- }
- @override
- Widget build(BuildContext context) {
- assert(!widget.primary || debugCheckHasMediaQuery(context));
- assert(debugCheckHasMaterialLocalizations(context));
- final ThemeData theme = Theme.of(context);
- final AppBarTheme appBarTheme = AppBarTheme.of(context);
- final ScaffoldState scaffold = Scaffold.of(context);
- final ModalRoute<dynamic>? parentRoute = ModalRoute.of(context);
- final bool hasDrawer = scaffold.hasDrawer;
- final bool hasEndDrawer = scaffold.hasEndDrawer;
- final bool canPop = parentRoute?.canPop ?? false;
- final bool useCloseButton =
- parentRoute is PageRoute<dynamic> && parentRoute.fullscreenDialog;
- final double toolbarHeight = widget.toolbarHeight ?? kToolbarHeight;
- IconThemeData overallIconTheme =
- widget.iconTheme ?? appBarTheme.iconTheme ?? theme.primaryIconTheme;
- IconThemeData actionsIconTheme = widget.actionsIconTheme ??
- appBarTheme.actionsIconTheme ??
- overallIconTheme;
- TextStyle? centerStyle = widget.textTheme?.titleLarge ??
- appBarTheme.toolbarTextStyle ??
- theme.primaryTextTheme.titleLarge;
- TextStyle? sideStyle = widget.textTheme?.bodyMedium ??
- appBarTheme.toolbarTextStyle ??
- theme.primaryTextTheme.bodyMedium;
- if (widget.toolbarOpacity != 1.0) {
- final double opacity =
- const Interval(0.25, 1.0, curve: Curves.fastOutSlowIn)
- .transform(widget.toolbarOpacity);
- if (centerStyle?.color != null)
- centerStyle = centerStyle!
- .copyWith(color: centerStyle.color!.withOpacity(opacity));
- if (sideStyle?.color != null)
- sideStyle =
- sideStyle!.copyWith(color: sideStyle.color!.withOpacity(opacity));
- overallIconTheme = overallIconTheme.copyWith(
- opacity: opacity * (overallIconTheme.opacity ?? 1.0));
- actionsIconTheme = actionsIconTheme.copyWith(
- opacity: opacity * (actionsIconTheme.opacity ?? 1.0));
- }
- Widget? leading = widget.leading;
- if (leading == null && widget.automaticallyImplyLeading) {
- if (hasDrawer) {
- leading = IconButton(
- icon: const Icon(Icons.menu),
- onPressed: _handleDrawerButton,
- tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
- );
- } else {
- if (canPop)
- leading = useCloseButton ? const CloseButton() : const BackButton();
- }
- }
- if (leading != null) {
- leading = ConstrainedBox(
- constraints: BoxConstraints.tightFor(
- width: widget.leadingWidth ?? _kLeadingWidth),
- child: leading,
- );
- }
- Widget? title = widget.title;
- if (title != null) {
- bool? namesRoute;
- switch (theme.platform) {
- case TargetPlatform.android:
- case TargetPlatform.fuchsia:
- case TargetPlatform.linux:
- case TargetPlatform.windows:
- namesRoute = true;
- break;
- case TargetPlatform.iOS:
- case TargetPlatform.macOS:
- break;
- }
- title = _WAppBarTitleBox(child: title);
- if (!widget.excludeHeaderSemantics) {
- title = Semantics(
- namesRoute: namesRoute,
- child: title,
- header: true,
- );
- }
- title = DefaultTextStyle(
- style: centerStyle!,
- softWrap: false,
- overflow: TextOverflow.ellipsis,
- child: title,
- );
- // Set maximum text scale factor to [_kMaxTitleTextScaleFactor] for the
- // title to keep the visual hierarchy the same even with larger font
- // sizes. To opt out, wrap the [title] widget in a [MediaQuery] widget
- // with [MediaQueryData.textScaleFactor] set to
- // `MediaQuery.textScaleFactorOf(context)`.
- final MediaQueryData mediaQueryData = MediaQuery.of(context);
- title = MediaQuery(
- data: mediaQueryData.copyWith(
- textScaleFactor: math.min(
- mediaQueryData.textScaleFactor,
- _kMaxTitleTextScaleFactor,
- ),
- ),
- child: title,
- );
- }
- Widget? actions;
- if (widget.actions != null && widget.actions!.isNotEmpty) {
- actions = Row(
- mainAxisSize: MainAxisSize.min,
- crossAxisAlignment: CrossAxisAlignment.stretch,
- children: widget.actions!,
- );
- } else if (hasEndDrawer) {
- actions = IconButton(
- icon: const Icon(Icons.menu),
- onPressed: _handleDrawerButtonEnd,
- tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip,
- );
- }
- // Allow the trailing actions to have their own theme if necessary.
- if (actions != null) {
- actions = IconTheme.merge(
- data: actionsIconTheme,
- child: actions,
- );
- }
- final Widget toolbar = NavigationToolbar(
- leading: leading,
- middle: title,
- trailing: actions,
- centerMiddle: widget._getEffectiveCenterTitle(theme)!,
- middleSpacing: widget.titleSpacing,
- );
- // If the toolbar is allocated less than toolbarHeight make it
- // appear to scroll upwards within its shrinking container.
- Widget appBar = ClipRect(
- child: CustomSingleChildLayout(
- delegate: _ToolbarContainerLayout(toolbarHeight),
- child: IconTheme.merge(
- data: overallIconTheme,
- child: DefaultTextStyle(
- style: sideStyle!,
- child: toolbar,
- ),
- ),
- ),
- );
- if (widget.bottom != null) {
- appBar = Column(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: <Widget>[
- Flexible(
- child: ConstrainedBox(
- constraints: BoxConstraints(maxHeight: toolbarHeight),
- child: appBar,
- ),
- ),
- if (widget.bottomOpacity == 1.0)
- widget.bottom!
- else
- Opacity(
- opacity: const Interval(0.25, 1.0, curve: Curves.fastOutSlowIn)
- .transform(widget.bottomOpacity),
- child: widget.bottom,
- ),
- ],
- );
- }
- // The padding applies to the toolbar and tabbar, not the flexible space.
- if (widget.primary) {
- appBar = SafeArea(
- bottom: false,
- top: true,
- child: appBar,
- );
- }
- appBar = Align(
- alignment: Alignment.topCenter,
- child: appBar,
- );
- if (widget.flexibleSpace != null) {
- appBar = Stack(
- fit: StackFit.passthrough,
- children: <Widget>[
- Semantics(
- sortKey: const OrdinalSortKey(1.0),
- explicitChildNodes: true,
- child: widget.flexibleSpace,
- ),
- Semantics(
- sortKey: const OrdinalSortKey(0.0),
- explicitChildNodes: true,
- // Creates a material widget to prevent the flexibleSpace from
- // obscuring the ink splashes produced by appBar children.
- child: Material(
- type: MaterialType.transparency,
- child: appBar,
- ),
- ),
- ],
- );
- }
- final Brightness brightness = widget.brightness ?? Brightness.light;
- final SystemUiOverlayStyle overlayStyle = brightness == Brightness.dark
- ? SystemUiOverlayStyle.light
- : SystemUiOverlayStyle.dark;
- return Semantics(
- container: true,
- child: AnnotatedRegion<SystemUiOverlayStyle>(
- value: overlayStyle,
- child: Material(
- color:
- widget.backgroundColor ?? appBarTheme.backgroundColor ?? theme.primaryColor,
- elevation:
- widget.elevation ?? appBarTheme.elevation ?? _defaultElevation,
- shadowColor: widget.shadowColor ??
- appBarTheme.shadowColor ??
- _defaultShadowColor,
- shape: widget.shape,
- child: Semantics(
- explicitChildNodes: true,
- child: appBar,
- ),
- ),
- ),
- );
- }
- }
- class _FloatingWAppBar extends StatefulWidget {
- const _FloatingWAppBar({Key? key, this.child}) : super(key: key);
- final Widget? child;
- @override
- _FloatingWAppBarState createState() => _FloatingWAppBarState();
- }
- // A wrapper for the widget created by _SliverWAppBarDelegate that starts and
- // stops the floating app bar's snap-into-view or snap-out-of-view animation.
- class _FloatingWAppBarState extends State<_FloatingWAppBar> {
- ScrollPosition? _position;
- @override
- void didChangeDependencies() {
- super.didChangeDependencies();
- if (_position != null)
- _position!.isScrollingNotifier.removeListener(_isScrollingListener);
- _position = Scrollable.of(context).position;
- if (_position != null)
- _position!.isScrollingNotifier.addListener(_isScrollingListener);
- }
- @override
- void dispose() {
- if (_position != null)
- _position!.isScrollingNotifier.removeListener(_isScrollingListener);
- super.dispose();
- }
- RenderSliverFloatingPersistentHeader? _headerRenderer() {
- return context
- .findAncestorRenderObjectOfType<RenderSliverFloatingPersistentHeader>();
- }
- void _isScrollingListener() {
- if (_position == null) return;
- // When a scroll stops, then maybe snap the appbar into view.
- // Similarly, when a scroll starts, then maybe stop the snap animation.
- final RenderSliverFloatingPersistentHeader? header = _headerRenderer();
- if (_position!.isScrollingNotifier.value)
- header?.maybeStopSnapAnimation(_position!.userScrollDirection);
- else
- header?.maybeStartSnapAnimation(_position!.userScrollDirection);
- }
- @override
- Widget build(BuildContext context) => widget.child!;
- }
- class _SliverWAppBarDelegate extends SliverPersistentHeaderDelegate {
- _SliverWAppBarDelegate({
- required this.leading,
- required this.automaticallyImplyLeading,
- required this.title,
- required this.actions,
- required this.flexibleSpace,
- required this.bottom,
- required this.elevation,
- required this.shadowColor,
- required this.forceElevated,
- required this.backgroundColor,
- required this.brightness,
- required this.iconTheme,
- required this.actionsIconTheme,
- required this.textTheme,
- required this.primary,
- required this.centerTitle,
- required this.excludeHeaderSemantics,
- required this.titleSpacing,
- required this.expandedHeight,
- required this.collapsedHeight,
- required this.topPadding,
- required this.floating,
- required this.pinned,
- required this.vsync,
- required this.snapConfiguration,
- required this.stretchConfiguration,
- required this.showOnScreenConfiguration,
- required this.shape,
- required this.toolbarHeight,
- required this.leadingWidth,
- }) : assert(primary! || topPadding == 0.0),
- assert(
- !floating! ||
- (snapConfiguration == null &&
- showOnScreenConfiguration == null),
- 'vsync cannot be null when snapConfiguration or showOnScreenConfiguration is not null, and floating is true',
- ),
- _bottomHeight = bottom?.preferredSize.height ?? 0.0;
- final Widget? leading;
- final bool automaticallyImplyLeading;
- final Widget? title;
- final List<Widget>? actions;
- final Widget? flexibleSpace;
- final PreferredSizeWidget? bottom;
- final double? elevation;
- final Color? shadowColor;
- final bool forceElevated;
- final Color? backgroundColor;
- final Brightness? brightness;
- final IconThemeData? iconTheme;
- final IconThemeData? actionsIconTheme;
- final TextTheme? textTheme;
- final bool? primary;
- final bool? centerTitle;
- final bool? excludeHeaderSemantics;
- final double? titleSpacing;
- final double? expandedHeight;
- final double? collapsedHeight;
- final double? topPadding;
- final bool? floating;
- final bool? pinned;
- final ShapeBorder? shape;
- final double? toolbarHeight;
- final double? leadingWidth;
- final double _bottomHeight;
- @override
- double get minExtent => collapsedHeight!;
- @override
- double get maxExtent => math.max(
- topPadding! +
- (expandedHeight ?? (toolbarHeight ?? kToolbarHeight) + _bottomHeight),
- minExtent);
- @override
- final TickerProvider vsync;
- @override
- final FloatingHeaderSnapConfiguration? snapConfiguration;
- @override
- final OverScrollHeaderStretchConfiguration? stretchConfiguration;
- @override
- final PersistentHeaderShowOnScreenConfiguration? showOnScreenConfiguration;
- @override
- Widget build(
- BuildContext context, double shrinkOffset, bool overlapsContent) {
- final double visibleMainHeight = maxExtent - shrinkOffset - topPadding!;
- final double extraToolbarHeight = math.max(
- minExtent -
- _bottomHeight -
- topPadding! -
- (toolbarHeight ?? kToolbarHeight),
- 0.0);
- final double visibleToolbarHeight =
- visibleMainHeight - _bottomHeight - extraToolbarHeight;
- final bool isPinnedWithOpacityFade =
- pinned! && floating! && bottom != null && extraToolbarHeight == 0.0;
- final double toolbarOpacity = !pinned! || isPinnedWithOpacityFade
- ? (visibleToolbarHeight / (toolbarHeight ?? kToolbarHeight))
- .clamp(0.0, 1.0)
- : 1.0;
- final Widget appBar = FlexibleSpaceBar.createSettings(
- minExtent: minExtent,
- maxExtent: maxExtent,
- currentExtent: math.max(minExtent, maxExtent - shrinkOffset),
- toolbarOpacity: toolbarOpacity,
- child: WAppBar(
- leading: leading,
- automaticallyImplyLeading: automaticallyImplyLeading,
- title: title,
- actions: actions,
- flexibleSpace:
- (title == null && flexibleSpace != null && !excludeHeaderSemantics!)
- ? Semantics(child: flexibleSpace, header: true)
- : flexibleSpace,
- bottom: bottom,
- elevation: forceElevated ||
- overlapsContent ||
- (pinned! && shrinkOffset > maxExtent - minExtent)
- ? elevation ?? 4.0
- : 0.0,
- shadowColor: shadowColor,
- backgroundColor: backgroundColor,
- brightness: brightness,
- iconTheme: iconTheme,
- actionsIconTheme: actionsIconTheme,
- textTheme: textTheme,
- primary: primary!,
- centerTitle: centerTitle!,
- excludeHeaderSemantics: excludeHeaderSemantics!,
- titleSpacing: titleSpacing!,
- shape: shape,
- toolbarOpacity: toolbarOpacity,
- bottomOpacity: pinned == true
- ? 1.0
- : (visibleMainHeight / _bottomHeight).clamp(0.0, 1.0),
- toolbarHeight: toolbarHeight,
- leadingWidth: leadingWidth,
- ),
- );
- return floating == true ? _FloatingWAppBar(child: appBar) : appBar;
- }
- @override
- bool shouldRebuild(covariant _SliverWAppBarDelegate oldDelegate) {
- return leading != oldDelegate.leading ||
- automaticallyImplyLeading != oldDelegate.automaticallyImplyLeading ||
- title != oldDelegate.title ||
- actions != oldDelegate.actions ||
- flexibleSpace != oldDelegate.flexibleSpace ||
- bottom != oldDelegate.bottom ||
- _bottomHeight != oldDelegate._bottomHeight ||
- elevation != oldDelegate.elevation ||
- shadowColor != oldDelegate.shadowColor ||
- backgroundColor != oldDelegate.backgroundColor ||
- brightness != oldDelegate.brightness ||
- iconTheme != oldDelegate.iconTheme ||
- actionsIconTheme != oldDelegate.actionsIconTheme ||
- textTheme != oldDelegate.textTheme ||
- primary != oldDelegate.primary ||
- centerTitle != oldDelegate.centerTitle ||
- titleSpacing != oldDelegate.titleSpacing ||
- expandedHeight != oldDelegate.expandedHeight ||
- topPadding != oldDelegate.topPadding ||
- pinned != oldDelegate.pinned ||
- floating != oldDelegate.floating ||
- vsync != oldDelegate.vsync ||
- snapConfiguration != oldDelegate.snapConfiguration ||
- stretchConfiguration != oldDelegate.stretchConfiguration ||
- showOnScreenConfiguration != oldDelegate.showOnScreenConfiguration ||
- forceElevated != oldDelegate.forceElevated ||
- toolbarHeight != oldDelegate.toolbarHeight ||
- leadingWidth != leadingWidth;
- }
- @override
- String toString() {
- return '${describeIdentity(this)}(topPadding: ${topPadding!.toStringAsFixed(1)}, bottomHeight: ${_bottomHeight.toStringAsFixed(1)}, ...)';
- }
- }
- /// A material design app bar that integrates with a [CustomScrollView].
- ///
- /// An app bar consists of a toolbar and potentially other widgets, such as a
- /// [TabBar] and a [FlexibleSpaceBar]. App bars typically expose one or more
- /// common actions with [IconButton]s which are optionally followed by a
- /// [PopupMenuButton] for less common operations.
- ///
- /// {@youtube 560 315 https://www.youtube.com/watch?v=R9C5KMJKluE}
- ///
- /// Sliver app bars are typically used as the first child of a
- /// [CustomScrollView], which lets the app bar integrate with the scroll view so
- /// that it can vary in height according to the scroll offset or float above the
- /// other content in the scroll view. For a fixed-height app bar at the top of
- /// the screen see [WAppBar], which is used in the [Scaffold.appBar] slot.
- ///
- /// The WAppBar 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.
- ///
- /// {@tool snippet}
- ///
- /// This is an example that could be included in a [CustomScrollView]'s
- /// [CustomScrollView.slivers] list:
- ///
- /// ```dart
- /// SliverWAppBar(
- /// expandedHeight: 150.0,
- /// flexibleSpace: const FlexibleSpaceBar(
- /// title: Text('Available seats'),
- /// ),
- /// actions: <Widget>[
- /// IconButton(
- /// icon: const Icon(Icons.add_circle),
- /// tooltip: 'Add new entry',
- /// onPressed: () { /* ... */ },
- /// ),
- /// ]
- /// )
- /// ```
- /// {@end-tool}
- ///
- /// ## Animated Examples
- ///
- /// The following animations show how app bars with different configurations
- /// behave when a user scrolls up and then down again.
- ///
- /// * App bar with [floating]: false, [pinned]: false, [snap]: false:
- /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar.mp4}
- ///
- /// * App bar with [floating]: true, [pinned]: false, [snap]: false:
- /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating.mp4}
- ///
- /// * App bar with [floating]: true, [pinned]: false, [snap]: true:
- /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating_snap.mp4}
- ///
- /// * App bar with [floating]: true, [pinned]: true, [snap]: false:
- /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_pinned_floating.mp4}
- ///
- /// * App bar with [floating]: true, [pinned]: true, [snap]: true:
- /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_pinned_floating_snap.mp4}
- ///
- /// * App bar with [floating]: false, [pinned]: true, [snap]: false:
- /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_pinned.mp4}
- ///
- /// The property [snap] can only be set to true if [floating] is also true.
- ///
- /// See also:
- ///
- /// * [CustomScrollView], which integrates the [SliverWAppBar] into its
- /// scrolling.
- /// * [WAppBar], which is a fixed-height app bar for use in [Scaffold.appBar].
- /// * [TabBar], which is typically placed in the [bottom] slot of the [WAppBar]
- /// if the screen has multiple pages arranged in tabs.
- /// * [IconButton], which is used with [actions] to show buttons on the app bar.
- /// * [PopupMenuButton], to show a popup menu on the app bar, via [actions].
- /// * [FlexibleSpaceBar], which is used with [flexibleSpace] when the app bar
- /// can expand and collapse.
- /// * <https://material.io/design/components/app-bars-top.html>
- class SliverWAppBar extends StatefulWidget {
- /// Creates a material design app bar that can be placed in a [CustomScrollView].
- ///
- /// The arguments [forceElevated], [primary], [floating], [pinned], [snap]
- /// and [automaticallyImplyLeading] must not be null.
- const SliverWAppBar({
- Key? key,
- this.leading,
- this.automaticallyImplyLeading = true,
- this.title,
- this.actions,
- this.flexibleSpace,
- this.bottom,
- this.elevation,
- this.shadowColor,
- this.forceElevated = false,
- this.backgroundColor,
- this.brightness,
- this.iconTheme,
- this.actionsIconTheme,
- this.textTheme,
- this.primary = true,
- this.centerTitle = true,
- this.excludeHeaderSemantics = false,
- this.titleSpacing = NavigationToolbar.kMiddleSpacing,
- this.collapsedHeight,
- this.expandedHeight,
- this.floating = false,
- this.pinned = false,
- this.snap = false,
- this.stretch = false,
- this.stretchTriggerOffset = 100.0,
- this.onStretchTrigger,
- this.shape,
- this.toolbarHeight = kToolbarHeight,
- this.leadingWidth,
- }) : assert(floating == true || snap == false,
- 'The "snap" argument only makes sense for floating app bars.'),
- // assert(collapsedHeight == null || collapsedHeight > toolbarHeight!,
- // 'The "collapsedHeight" argument has to be larger than [toolbarHeight].'),
- super(key: key);
- /// A widget to display before the [title].
- ///
- /// If this is null and [automaticallyImplyLeading] is set to true, the [WAppBar] will
- /// imply an appropriate widget. For example, if the [WAppBar] is in a [Scaffold]
- /// that 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 [WAppBar] will use a [BackButton] that calls
- /// [Navigator.maybePop].
- final Widget? leading;
- /// Controls whether we should try to imply the leading widget if null.
- ///
- /// If true and [leading] is null, automatically try to deduce what the leading
- /// widget should be. If false and [leading] is null, leading space is given to [title].
- /// If leading widget is not null, this parameter has no effect.
- final bool? automaticallyImplyLeading;
- /// The primary widget displayed in the app bar.
- ///
- /// Typically a [Text] widget containing a description of the current contents
- /// of the app.
- final Widget? title;
- /// Widgets to display after the [title] widget.
- ///
- /// Typically these widgets are [IconButton]s representing common operations.
- /// For less common operations, consider using a [PopupMenuButton] as the
- /// last action.
- ///
- /// {@tool snippet}
- ///
- /// ```dart
- /// Scaffold(
- /// body: CustomScrollView(
- /// primary: true,
- /// slivers: <Widget>[
- /// SliverWAppBar(
- /// title: Text('Hello World'),
- /// actions: <Widget>[
- /// IconButton(
- /// icon: Icon(Icons.shopping_cart),
- /// tooltip: 'Open shopping cart',
- /// onPressed: () {
- /// // handle the press
- /// },
- /// ),
- /// ],
- /// ),
- /// // ...rest of body...
- /// ],
- /// ),
- /// )
- /// ```
- /// {@end-tool}
- final List<Widget>? actions;
- /// This widget is stacked behind the toolbar and the tab bar. It's height will
- /// be the same as the app bar's overall height.
- ///
- /// When using [SliverWAppBar.flexibleSpace], the [SliverWAppBar.expandedHeight]
- /// must be large enough to accommodate the [SliverWAppBar.flexibleSpace] widget.
- ///
- /// Typically a [FlexibleSpaceBar]. See [FlexibleSpaceBar] for details.
- final Widget? flexibleSpace;
- /// This widget appears across the bottom of the app bar.
- ///
- /// Typically a [TabBar]. Only widgets that implement [PreferredSizeWidget] can
- /// be used at the bottom of an app bar.
- ///
- /// See also:
- ///
- /// * [PreferredSize], which can be used to give an arbitrary widget a preferred size.
- final PreferredSizeWidget? bottom;
- /// The z-coordinate at which to place this app bar when it is above other
- /// content. This controls the size of the shadow below the app bar.
- ///
- /// If this property is null, then [AppBarTheme.elevation] of
- /// [ThemeData.appBarTheme] is used, if that is also null, the default value
- /// is 4.
- ///
- /// If [forceElevated] is false, the elevation is ignored when the app bar has
- /// no content underneath it. For example, if the app bar is [pinned] but no
- /// content is scrolled under it, or if it scrolls with the content, then no
- /// shadow is drawn, regardless of the value of [elevation].
- final double? elevation;
- /// The color to paint the shadow below the app bar. Typically this should be set
- /// along with [elevation].
- ///
- /// If this property is null, then [AppBarTheme.shadowColor] of
- /// [ThemeData.appBarTheme] is used, if that is also null, the default value
- /// is fully opaque black.
- final Color? shadowColor;
- /// Whether to show the shadow appropriate for the [elevation] even if the
- /// content is not scrolled under the [WAppBar].
- ///
- /// Defaults to false, meaning that the [elevation] is only applied when the
- /// [WAppBar] is being displayed over content that is scrolled under it.
- ///
- /// When set to true, the [elevation] is applied regardless.
- ///
- /// Ignored when [elevation] is zero.
- final bool? forceElevated;
- /// The color to use for the app bar's material. Typically this should be set
- /// along with [brightness], [iconTheme], [textTheme].
- ///
- /// If this property is null, then [AppBarTheme.backgroundColor] of
- /// [ThemeData.appBarTheme] is used. If that is also null, then
- /// [ThemeData.primaryColor] is used.
- final Color? backgroundColor;
- /// The brightness of the app bar's material. Typically this is set along
- /// with [backgroundColor], [iconTheme], [textTheme].
- ///
- final Brightness? brightness;
- /// The color, opacity, and size to use for app bar icons. Typically this
- /// is set along with [backgroundColor], [brightness], [textTheme].
- ///
- /// If this property is null, then [AppBarTheme.iconTheme] of
- /// [ThemeData.appBarTheme] is used, if that is also null, then
- /// [ThemeData.primaryIconTheme] is used.
- final IconThemeData? iconTheme;
- /// The color, opacity, and size to use for trailing app bar icons. This
- /// should only be used when the trailing icons should be themed differently
- /// than the leading icons.
- ///
- /// If this property is null, then [AppBarTheme.actionsIconTheme] of
- /// [ThemeData.appBarTheme] is used, if that is also null, then this falls
- /// back to [iconTheme].
- final IconThemeData? actionsIconTheme;
- /// The typographic styles to use for text in the app bar. Typically this is
- /// set along with [brightness] [backgroundColor], [iconTheme].
- ///
- /// If this property is null, then [AppBarTheme.textTheme] of
- /// [ThemeData.appBarTheme] is used, if that is also null, then
- /// [ThemeData.primaryTextTheme] is used.
- final TextTheme? textTheme;
- /// Whether this app bar is being displayed at the top of the screen.
- ///
- /// If this is true, the top padding specified by the [MediaQuery] will be
- /// added to the top of the toolbar.
- final bool? primary;
- /// Whether the title should be centered.
- ///
- /// Defaults to being adapted to the current [TargetPlatform].
- final bool? centerTitle;
- /// Whether the title should be wrapped with header [Semantics].
- ///
- /// Defaults to false.
- final bool? excludeHeaderSemantics;
- /// The spacing around [title] content on the horizontal axis. This spacing is
- /// applied even if there is no [leading] content or [actions]. If you want
- /// [title] to take all the space available, set this value to 0.0.
- ///
- /// Defaults to [NavigationToolbar.kMiddleSpacing].
- final double? titleSpacing;
- /// Defines the height of the app bar when it is collapsed.
- ///
- /// By default, the collapsed height is [toolbarHeight]. If [bottom] widget is
- /// specified, then its height from [PreferredSizeWidget.preferredSize] is
- /// added to the height. If [primary] is true, then the [MediaQuery] top
- /// padding, [EdgeInsets.top] of [MediaQueryData.padding], is added as well.
- ///
- /// If [pinned] and [floating] are true, with [bottom] set, the default
- /// collapsed height is only the height of [PreferredSizeWidget.preferredSize]
- /// with the [MediaQuery] top padding.
- final double? collapsedHeight;
- /// The size of the app bar when it is fully expanded.
- ///
- /// By default, the total height of the toolbar and the bottom widget (if
- /// any). If a [flexibleSpace] widget is specified this height should be big
- /// enough to accommodate whatever that widget contains.
- ///
- /// This does not include the status bar height (which will be automatically
- /// included if [primary] is true).
- final double? expandedHeight;
- /// Whether the app bar should become visible as soon as the user scrolls
- /// towards the app bar.
- ///
- /// Otherwise, the user will need to scroll near the top of the scroll view to
- /// reveal the app bar.
- ///
- /// If [snap] is true then a scroll that exposes the app bar will trigger an
- /// animation that slides the entire app bar into view. Similarly if a scroll
- /// dismisses the app bar, the animation will slide it completely out of view.
- ///
- /// ## Animated Examples
- ///
- /// The following animations show how the app bar changes its scrolling
- /// behavior based on the value of this property.
- ///
- /// * App bar with [floating] set to false:
- /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar.mp4}
- /// * App bar with [floating] set to true:
- /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating.mp4}
- ///
- /// See also:
- ///
- /// * [SliverWAppBar] for more animated examples of how this property changes the
- /// behavior of the app bar in combination with [pinned] and [snap].
- final bool? floating;
- /// Whether the app bar should remain visible at the start of the scroll view.
- ///
- /// The app bar can still expand and contract as the user scrolls, but it will
- /// remain visible rather than being scrolled out of view.
- ///
- /// ## Animated Examples
- ///
- /// The following animations show how the app bar changes its scrolling
- /// behavior based on the value of this property.
- ///
- /// * App bar with [pinned] set to false:
- /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar.mp4}
- /// * App bar with [pinned] set to true:
- /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_pinned.mp4}
- ///
- /// See also:
- ///
- /// * [SliverWAppBar] for more animated examples of how this property changes the
- /// behavior of the app bar in combination with [floating].
- final bool? pinned;
- /// The material's shape as well as its shadow.
- ///
- /// A shadow is only displayed if the [elevation] is greater than zero.
- final ShapeBorder? shape;
- /// If [snap] and [floating] are true then the floating app bar will "snap"
- /// into view.
- ///
- /// If [snap] is true then a scroll that exposes the floating app bar will
- /// trigger an animation that slides the entire app bar into view. Similarly
- /// if a scroll dismisses the app bar, the animation will slide the app bar
- /// completely out of view. Additionally, setting [snap] to true will fully
- /// expand the floating app bar when the framework tries to reveal the
- /// contents of the app bar by calling [RenderObject.showOnScreen]. For
- /// example, when a [TextField] in the floating app bar gains focus, if [snap]
- /// is true, the framework will always fully expand the floating app bar, in
- /// order to reveal the focused [TextField].
- ///
- /// Snapping only applies when the app bar is floating, not when the app bar
- /// appears at the top of its scroll view.
- ///
- /// ## Animated Examples
- ///
- /// The following animations show how the app bar changes its scrolling
- /// behavior based on the value of this property.
- ///
- /// * App bar with [snap] set to false:
- /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating.mp4}
- /// * App bar with [snap] set to true:
- /// {@animation 476 400 https://flutter.github.io/assets-for-api-docs/assets/material/app_bar_floating_snap.mp4}
- ///
- /// See also:
- ///
- /// * [SliverWAppBar] for more animated examples of how this property changes the
- /// behavior of the app bar in combination with [pinned] and [floating].
- final bool? snap;
- /// Whether the app bar should stretch to fill the over-scroll area.
- ///
- /// The app bar can still expand and contract as the user scrolls, but it will
- /// also stretch when the user over-scrolls.
- final bool? stretch;
- /// The offset of overscroll required to activate [onStretchTrigger].
- ///
- /// This defaults to 100.0.
- final double? stretchTriggerOffset;
- /// The callback function to be executed when a user over-scrolls to the
- /// offset specified by [stretchTriggerOffset].
- final AsyncCallback? onStretchTrigger;
- /// Defines the height of the toolbar component of an [WAppBar].
- ///
- /// By default, the value of `toolbarHeight` is [kToolbarHeight].
- final double? toolbarHeight;
- /// Defines the width of [leading] widget.
- ///
- /// By default, the value of `leadingWidth` is 56.0.
- final double? leadingWidth;
- @override
- _SliverWAppBarState createState() => _SliverWAppBarState();
- }
- // This class is only Stateful because it owns the TickerProvider used
- // by the floating appbar snap animation (via FloatingHeaderSnapConfiguration).
- class _SliverWAppBarState extends State<SliverWAppBar>
- with TickerProviderStateMixin {
- FloatingHeaderSnapConfiguration? _snapConfiguration;
- OverScrollHeaderStretchConfiguration? _stretchConfiguration;
- PersistentHeaderShowOnScreenConfiguration? _showOnScreenConfiguration;
- void _updateSnapConfiguration() {
- if (widget.snap == true && widget.floating == true) {
- _snapConfiguration = FloatingHeaderSnapConfiguration(
- curve: Curves.easeOut,
- duration: const Duration(milliseconds: 200),
- );
- } else {
- _snapConfiguration = null;
- }
- _showOnScreenConfiguration = widget.floating == true && widget.snap == true
- ? const PersistentHeaderShowOnScreenConfiguration(
- minShowOnScreenExtent: double.infinity)
- : null;
- }
- void _updateStretchConfiguration() {
- if (widget.stretch == true) {
- _stretchConfiguration = OverScrollHeaderStretchConfiguration(
- stretchTriggerOffset: widget.stretchTriggerOffset!,
- onStretchTrigger: widget.onStretchTrigger,
- );
- } else {
- _stretchConfiguration = null;
- }
- }
- @override
- void initState() {
- super.initState();
- _updateSnapConfiguration();
- _updateStretchConfiguration();
- }
- @override
- void didUpdateWidget(SliverWAppBar oldWidget) {
- super.didUpdateWidget(oldWidget);
- if (widget.snap != oldWidget.snap || widget.floating != oldWidget.floating)
- _updateSnapConfiguration();
- if (widget.stretch != oldWidget.stretch) _updateStretchConfiguration();
- }
- @override
- Widget build(BuildContext context) {
- assert(!widget.primary! || debugCheckHasMediaQuery(context));
- final double bottomHeight = widget.bottom?.preferredSize.height ?? 0.0;
- final double topPadding =
- widget.primary == true ? MediaQuery.of(context).padding.top : 0.0;
- final double collapsedHeight = (widget.pinned == true &&
- widget.floating == true &&
- widget.bottom != null)
- ? (widget.collapsedHeight ?? 0.0) + bottomHeight + topPadding
- : (widget.collapsedHeight!) + bottomHeight + topPadding;
- return MediaQuery.removePadding(
- context: context,
- removeBottom: true,
- child: SliverPersistentHeader(
- floating: widget.floating!,
- pinned: widget.pinned!,
- delegate: _SliverWAppBarDelegate(
- vsync: this,
- leading: widget.leading,
- automaticallyImplyLeading: widget.automaticallyImplyLeading!,
- title: widget.title,
- actions: widget.actions,
- flexibleSpace: widget.flexibleSpace,
- bottom: widget.bottom,
- elevation: widget.elevation,
- shadowColor: widget.shadowColor,
- forceElevated: widget.forceElevated!,
- backgroundColor: widget.backgroundColor!,
- brightness: widget.brightness,
- iconTheme: widget.iconTheme,
- actionsIconTheme: widget.actionsIconTheme,
- textTheme: widget.textTheme,
- primary: widget.primary,
- centerTitle: widget.centerTitle,
- excludeHeaderSemantics: widget.excludeHeaderSemantics,
- titleSpacing: widget.titleSpacing,
- expandedHeight: widget.expandedHeight,
- collapsedHeight: collapsedHeight,
- topPadding: topPadding,
- floating: widget.floating,
- pinned: widget.pinned,
- shape: widget.shape,
- snapConfiguration: _snapConfiguration,
- stretchConfiguration: _stretchConfiguration,
- showOnScreenConfiguration: _showOnScreenConfiguration,
- toolbarHeight: widget.toolbarHeight,
- leadingWidth: widget.leadingWidth,
- ),
- ),
- );
- }
- }
- // Layout the WAppBar's title with unconstrained height, vertically
- // center it within its (NavigationToolbar) parent, and allow the
- // parent to constrain the title's actual height.
- class _WAppBarTitleBox extends SingleChildRenderObjectWidget {
- const _WAppBarTitleBox({Key? key, required Widget child})
- : super(key: key, child: child);
- @override
- _RenderWAppBarTitleBox createRenderObject(BuildContext context) {
- return _RenderWAppBarTitleBox(
- textDirection: Directionality.of(context),
- );
- }
- @override
- void updateRenderObject(
- BuildContext context, _RenderWAppBarTitleBox renderObject) {
- renderObject.textDirection = Directionality.of(context);
- }
- }
- class _RenderWAppBarTitleBox extends RenderAligningShiftedBox {
- _RenderWAppBarTitleBox({
- RenderBox? child,
- TextDirection? textDirection,
- }) : super(
- child: child,
- alignment: Alignment.center,
- textDirection: textDirection);
- @override
- void performLayout() {
- final BoxConstraints constraints = this.constraints;
- final BoxConstraints innerConstraints =
- constraints.copyWith(maxHeight: double.infinity);
- child!.layout(innerConstraints, parentUsesSize: true);
- size = constraints.constrain(child!.size);
- alignChild();
- }
- }
|