From 3ff78278db97d988281ce4139c4cf173d4395983 Mon Sep 17 00:00:00 2001 From: joostlek Date: Wed, 8 Aug 2018 11:14:18 +0200 Subject: [PATCH] Initial commit --- .gitignore | 10 + CHANGELOG.md | 3 + LICENSE | 1 + README.md | 9 + gradient_app_bar.iml | 19 + lib/gradient_app_bar.dart | 948 ++++++++++++++++++++++++++++++++++++++ pubspec.lock | 377 +++++++++++++++ pubspec.yaml | 50 ++ 8 files changed, 1417 insertions(+) create mode 100644 .gitignore create mode 100644 CHANGELOG.md create mode 100644 LICENSE create mode 100644 README.md create mode 100644 gradient_app_bar.iml create mode 100644 lib/gradient_app_bar.dart create mode 100644 pubspec.lock create mode 100644 pubspec.yaml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..446ed0d --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +build/ +ios/.generated/ +ios/Flutter/Generated.xcconfig +ios/Runner/GeneratedPluginRegistrant.* diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..ac07159 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +## [0.0.1] - TODO: Add release date. + +* TODO: Describe initial release. diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..ba75c69 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +TODO: Add your license here. diff --git a/README.md b/README.md new file mode 100644 index 0000000..7c6e4c0 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# gradient_app_bar + +Gradient app bar for flutter + +## Getting Started + +For help getting started with Flutter, view our online [documentation](https://flutter.io/). + +For help on editing package code, view the [documentation](https://flutter.io/developing-packages/). diff --git a/gradient_app_bar.iml b/gradient_app_bar.iml new file mode 100644 index 0000000..8d48a06 --- /dev/null +++ b/gradient_app_bar.iml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/lib/gradient_app_bar.dart b/lib/gradient_app_bar.dart new file mode 100644 index 0000000..8292ef2 --- /dev/null +++ b/lib/gradient_app_bar.dart @@ -0,0 +1,948 @@ +library gradient_app_bar; + +import 'dart:math' as math; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter/widgets.dart'; + +const double _kLeadingWidth = kToolbarHeight; + +class _ToolbarContainerLayout extends SingleChildLayoutDelegate { + const _ToolbarContainerLayout(); + + @override + BoxConstraints getConstraintsForChild(BoxConstraints constraints) { + return constraints.tighten(height: kToolbarHeight); + } + + @override + Size getSize(BoxConstraints constraints) { + return new Size(constraints.maxWidth, kToolbarHeight); + } + + @override + Offset getPositionForChild(Size size, Size childSize) { + return new Offset(0.0, size.height - childSize.height); + } + + @override + bool shouldRelayout(_ToolbarContainerLayout oldDelegate) => false; +} + +class GradientAppBar extends StatefulWidget implements PreferredSizeWidget { + GradientAppBar({ + Key key, + this.leading, + this.automaticallyImplyLeading = true, + this.title, + this.actions, + this.flexibleSpace, + this.bottom, + this.elevation = 4.0, + this.backgroundColorStart, + this.backgroundColorEnd, + this.brightness, + this.iconTheme, + this.textTheme, + this.primary = true, + this.centerTitle, + this.titleSpacing = NavigationToolbar.kMiddleSpacing, + this.toolbarOpacity = 1.0, + this.bottomOpacity = 1.0, + }) : assert(automaticallyImplyLeading != null), + assert(elevation != null), + assert(primary != null), + assert(titleSpacing != null), + assert(toolbarOpacity != null), + assert(bottomOpacity != null), + preferredSize = new Size.fromHeight( + kToolbarHeight + (bottom?.preferredSize?.height ?? 0.0)), + super(key: key); + 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 appbar. + /// + /// 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. + /// + /// ## Sample code + /// + /// ```dart + /// new Scaffold( + /// appBar: new AppBar( + /// title: new Text('Hello World'), + /// actions: [ + /// new IconButton( + /// icon: new Icon(Icons.shopping_cart), + /// tooltip: 'Open shopping cart', + /// onPressed: () { + /// // ... + /// }, + /// ), + /// ], + /// ), + /// ) + /// ``` + final List actions; + + /// This widget is stacked behind the toolbar and the tabbar. It's height will + /// be the same as the app bar's overall height. + /// + /// A flexible space isn't actually flexible unless the [AppBar]'s container + /// changes the [AppBar]'s size. A [SliverAppBar] in a [CustomScrollView] + /// changes the [AppBar]'s height when scrolled. + /// + /// 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. This controls the size of + /// the shadow below the app bar. + /// + /// Defaults to 4, the appropriate elevation for app bars. + final double elevation; + + /// The color to use for the app bar's material. Typically this should be set + /// along with [brightness], [iconTheme], [textTheme]. + /// + /// Defaults to [ThemeData.primaryColor]. + final Color backgroundColorStart; + final Color backgroundColorEnd; + + /// The brightness of the app bar's material. Typically this is set along + /// with [backgroundColor], [iconTheme], [textTheme]. + /// + /// Defaults to [ThemeData.primaryColorBrightness]. + final Brightness brightness; + + /// The color, opacity, and size to use for app bar icons. Typically this + /// is set along with [backgroundColor], [brightness], [textTheme]. + /// + /// Defaults to [ThemeData.primaryIconTheme]. + final IconThemeData iconTheme; + + /// The typographic styles to use for text in the app bar. Typically this is + /// set along with [brightness] [backgroundColor], [iconTheme]. + /// + /// Defaults to [ThemeData.primaryTextTheme]. + final TextTheme textTheme; + + /// Whether this app bar is being displayed at the top of the screen. + /// + /// If true, the appbar'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. + /// + /// Defaults to being adapted to the current [TargetPlatform]. + final bool centerTitle; + + /// 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 [SliverAppBar] 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 [SliverAppBar] to animate the opacity of the toolbar when the app + /// bar is scrolled. + final double bottomOpacity; + + /// A size whose height is the sum of [kToolbarHeight] and the [bottom] widget's + /// preferred height. + /// + /// [Scaffold] uses this this size to set its app bar's height. + @override + final Size preferredSize; + + bool _getEffectiveCenterTitle(ThemeData themeData) { + if (centerTitle != null) return centerTitle; + assert(themeData.platform != null); + switch (themeData.platform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + return false; + case TargetPlatform.iOS: + return actions == null || actions.length < 2; + } + return null; + } + + @override + _GradientAppBarState createState() => new _GradientAppBarState(); +} + +class _GradientAppBarState extends State { + void _handleDrawerButton() { + Scaffold.of(context).openDrawer(); + } + + void _handleDrawerButtonEnd() { + Scaffold.of(context).openEndDrawer(); + } + + @override + Widget build(BuildContext context) { + assert(!widget.primary || debugCheckHasMediaQuery(context)); + final ThemeData themeData = Theme.of(context); + final ScaffoldState scaffold = Scaffold.of(context, nullOk: true); + final ModalRoute parentRoute = ModalRoute.of(context); + + final bool hasDrawer = scaffold?.hasDrawer ?? false; + final bool hasEndDrawer = scaffold?.hasEndDrawer ?? false; + final bool canPop = parentRoute?.canPop ?? false; + final bool useCloseButton = + parentRoute is PageRoute && parentRoute.fullscreenDialog; + + IconThemeData appBarIconTheme = + widget.iconTheme ?? themeData.primaryIconTheme; + TextStyle centerStyle = + widget.textTheme?.title ?? themeData.primaryTextTheme.title; + TextStyle sideStyle = + widget.textTheme?.body1 ?? themeData.primaryTextTheme.body1; + + 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)); + appBarIconTheme = appBarIconTheme.copyWith( + opacity: opacity * (appBarIconTheme.opacity ?? 1.0)); + } + + Widget leading = widget.leading; + if (leading == null && widget.automaticallyImplyLeading) { + if (hasDrawer) { + leading = new 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 = new ConstrainedBox( + constraints: const BoxConstraints.tightFor(width: _kLeadingWidth), + child: leading, + ); + } + + Widget title = widget.title; + if (title != null) { + bool namesRoute; + switch (defaultTargetPlatform) { + case TargetPlatform.android: + case TargetPlatform.fuchsia: + namesRoute = true; + break; + case TargetPlatform.iOS: + break; + } + title = new DefaultTextStyle( + style: centerStyle, + softWrap: false, + overflow: TextOverflow.ellipsis, + child: new Semantics( + namesRoute: namesRoute, + child: title, + header: true, + ), + ); + } + + Widget actions; + if (widget.actions != null && widget.actions.isNotEmpty) { + actions = new Row( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, + children: widget.actions, + ); + } else if (hasEndDrawer) { + actions = new IconButton( + icon: const Icon(Icons.menu), + onPressed: _handleDrawerButtonEnd, + tooltip: MaterialLocalizations.of(context).openAppDrawerTooltip, + ); + } + + final Widget toolbar = new NavigationToolbar( + leading: leading, + middle: title, + trailing: actions, + centerMiddle: widget._getEffectiveCenterTitle(themeData), + middleSpacing: widget.titleSpacing, + ); + + // If the toolbar is allocated less than kToolbarHeight make it + // appear to scroll upwards within its shrinking container. + Widget appBar = new ClipRect( + child: new CustomSingleChildLayout( + delegate: const _ToolbarContainerLayout(), + child: IconTheme.merge( + data: appBarIconTheme, + child: new DefaultTextStyle( + style: sideStyle, + child: toolbar, + ), + ), + ), + ); + if (widget.bottom != null) { + appBar = new Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + new Flexible( + child: new ConstrainedBox( + constraints: const BoxConstraints(maxHeight: kToolbarHeight), + child: appBar, + ), + ), + widget.bottomOpacity == 1.0 + ? widget.bottom + : new 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 = new SafeArea( + top: true, + child: appBar, + ); + } + + appBar = new Align( + alignment: Alignment.topCenter, + child: appBar, + ); + + if (widget.flexibleSpace != null) { + appBar = new Stack( + fit: StackFit.passthrough, + children: [ + widget.flexibleSpace, + appBar, + ], + ); + } + final Brightness brightness = + widget.brightness ?? themeData.primaryColorBrightness; + final SystemUiOverlayStyle overlayStyle = brightness == Brightness.dark + ? SystemUiOverlayStyle.light + : SystemUiOverlayStyle.dark; + + return new Semantics( + container: true, + explicitChildNodes: true, + child: new AnnotatedRegion( + value: overlayStyle, + child: new Material( + color: Color(0x00000000), + elevation: widget.elevation, + child: Container( + decoration: BoxDecoration( + gradient: LinearGradient( + colors: [ + widget.backgroundColorStart ?? themeData.primaryColor, + widget.backgroundColorEnd ?? themeData.accentColor, + ], + begin: FractionalOffset(0.0, 0.0), + end: FractionalOffset(1.0, 0.0), + stops: [0.0, 1.0], + tileMode: TileMode.clamp), + ), + child: appBar, + ), + ), + ), + ); + } +} + +class _FloatingAppBar extends StatefulWidget { + const _FloatingAppBar({Key key, this.child}) : super(key: key); + + final Widget child; + + @override + _FloatingAppBarState createState() => new _FloatingAppBarState(); +} + +// A wrapper for the widget created by _SliverAppBarDelegate that starts and +/// stops the floating appbar's snap-into-view or snap-out-of-view animation. +class _FloatingAppBarState extends State<_FloatingAppBar> { + 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.ancestorRenderObjectOfType( + const TypeMatcher()); + } + + 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 _SliverGradientAppBarDelegate extends SliverPersistentHeaderDelegate { + _SliverGradientAppBarDelegate({ + @required this.leading, + @required this.automaticallyImplyLeading, + @required this.title, + @required this.actions, + @required this.flexibleSpace, + @required this.bottom, + @required this.elevation, + @required this.forceElevated, + @required this.backgroundColorStart, + @required this.backgroundColorEnd, + @required this.brightness, + @required this.iconTheme, + @required this.textTheme, + @required this.primary, + @required this.centerTitle, + @required this.titleSpacing, + @required this.expandedHeight, + @required this.collapsedHeight, + @required this.topPadding, + @required this.floating, + @required this.pinned, + @required this.snapConfiguration, + }) : assert(primary || topPadding == 0.0), + _bottomHeight = bottom?.preferredSize?.height ?? 0.0; + + final Widget leading; + final bool automaticallyImplyLeading; + final Widget title; + final List actions; + final Widget flexibleSpace; + final PreferredSizeWidget bottom; + final double elevation; + final bool forceElevated; + final Color backgroundColorStart; + final Color backgroundColorEnd; + final Brightness brightness; + final IconThemeData iconTheme; + final TextTheme textTheme; + final bool primary; + final bool centerTitle; + final double titleSpacing; + final double expandedHeight; + final double collapsedHeight; + final double topPadding; + final bool floating; + final bool pinned; + + final double _bottomHeight; + + @override + double get minExtent => + collapsedHeight ?? (topPadding + kToolbarHeight + _bottomHeight); + + @override + double get maxExtent => math.max( + topPadding + (expandedHeight ?? kToolbarHeight + _bottomHeight), + minExtent); + + @override + final FloatingHeaderSnapConfiguration snapConfiguration; + + @override + Widget build( + BuildContext context, double shrinkOffset, bool overlapsContent) { + final double visibleMainHeight = maxExtent - shrinkOffset - topPadding; + final double toolbarOpacity = pinned && !floating + ? 1.0 + : ((visibleMainHeight - _bottomHeight) / kToolbarHeight) + .clamp(0.0, 1.0); + final Widget appBar = FlexibleSpaceBar.createSettings( + minExtent: minExtent, + maxExtent: maxExtent, + currentExtent: math.max(minExtent, maxExtent - shrinkOffset), + toolbarOpacity: toolbarOpacity, + child: new GradientAppBar( + leading: leading, + automaticallyImplyLeading: automaticallyImplyLeading, + title: title, + actions: actions, + flexibleSpace: (title == null && flexibleSpace != null) + ? new Semantics(child: flexibleSpace, header: true) + : flexibleSpace, + bottom: bottom, + elevation: forceElevated || + overlapsContent || + (pinned && shrinkOffset > maxExtent - minExtent) + ? elevation ?? 4.0 + : 0.0, + backgroundColorStart: backgroundColorStart, + backgroundColorEnd: backgroundColorEnd, + brightness: brightness, + iconTheme: iconTheme, + textTheme: textTheme, + primary: primary, + centerTitle: centerTitle, + titleSpacing: titleSpacing, + toolbarOpacity: toolbarOpacity, + bottomOpacity: + pinned ? 1.0 : (visibleMainHeight / _bottomHeight).clamp(0.0, 1.0), + ), + ); + return floating ? new _FloatingAppBar(child: appBar) : appBar; + } + + @override + bool shouldRebuild(covariant _SliverGradientAppBarDelegate 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 || + backgroundColorStart != oldDelegate.backgroundColorStart || + backgroundColorEnd != oldDelegate.backgroundColorEnd || + brightness != oldDelegate.brightness || + iconTheme != oldDelegate.iconTheme || + textTheme != oldDelegate.textTheme || + primary != oldDelegate.primary || + centerTitle != oldDelegate.centerTitle || + titleSpacing != oldDelegate.titleSpacing || + expandedHeight != oldDelegate.expandedHeight || + topPadding != oldDelegate.topPadding || + pinned != oldDelegate.pinned || + floating != oldDelegate.floating || + snapConfiguration != oldDelegate.snapConfiguration; + } + + @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. +/// +/// 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 [AppBar], which is used in the [Scaffold.appBar] slot. +/// +/// 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. +/// +/// ## Sample code +/// +/// This is an example that could be included in a [CustomScrollView]'s +/// [CustomScrollView.slivers] list: +/// +/// ```dart +/// new SliverAppBar( +/// expandedHeight: 150.0, +/// flexibleSpace: const FlexibleSpaceBar( +/// title: const Text('Available seats'), +/// ), +/// actions: [ +/// new IconButton( +/// icon: const Icon(Icons.add_circle), +/// tooltip: 'Add new entry', +/// onPressed: () { /* ... */ }, +/// ), +/// ] +/// ) +/// ``` +/// +/// See also: +/// +/// * [CustomScrollView], which integrates the [SliverAppBar] into its +/// scrolling. +/// * [AppBar], which is a fixed-height app bar for use in [Scaffold.appBar]. +/// * [TabBar], which is typically placed in the [bottom] slot of the [AppBar] +/// 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. +/// * +class SliverGradientAppBar 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 SliverGradientAppBar({ + Key key, + this.leading, + this.automaticallyImplyLeading = true, + this.title, + this.actions, + this.flexibleSpace, + this.bottom, + this.elevation, + this.forceElevated = false, + this.backgroundColorStart, + this.backgroundColorEnd, + this.brightness, + this.iconTheme, + this.textTheme, + this.primary = true, + this.centerTitle, + this.titleSpacing = NavigationToolbar.kMiddleSpacing, + this.expandedHeight, + this.floating = false, + this.pinned = false, + this.snap = false, + }) : assert(automaticallyImplyLeading != null), + assert(forceElevated != null), + assert(primary != null), + assert(titleSpacing != null), + assert(floating != null), + assert(pinned != null), + assert(snap != null), + assert(floating || !snap, + 'The "snap" argument only makes sense for floating app bars.'), + super(key: key); + + /// A widget to display before the [title]. + /// + /// If this is null and [automaticallyImplyLeading] is set to true, the [AppBar] will + /// imply an appropriate widget. For example, if the [AppBar] 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 [AppBar] 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 appbar. + /// + /// 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. + /// + /// ## Sample code + /// + /// ```dart + /// new Scaffold( + /// body: new CustomScrollView( + /// primary: true, + /// slivers: [ + /// new SliverAppBar( + /// title: new Text('Hello World'), + /// actions: [ + /// new IconButton( + /// icon: new Icon(Icons.shopping_cart), + /// tooltip: 'Open shopping cart', + /// onPressed: () { + /// // handle the press + /// }, + /// ), + /// ], + /// ), + /// // ...rest of body... + /// ], + /// ), + /// ) + /// ``` + final List actions; + + /// This widget is stacked behind the toolbar and the tabbar. It's height will + /// be the same as the app bar's overall height. + /// + /// Typically a [FlexibleSpaceBar]. See [FlexibleSpaceBar] for details. + final Widget flexibleSpace; + + /// This widget appears across the bottom of the appbar. + /// + /// 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. + /// + /// Defaults to 4, the appropriate elevation for app bars. + /// + /// 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; + + /// Whether to show the shadow appropriate for the [elevation] even if the + /// content is not scrolled under the [AppBar]. + /// + /// Defaults to false, meaning that the [elevation] is only applied when the + /// [AppBar] 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]. + /// + /// Defaults to [ThemeData.primaryColor]. + final Color backgroundColorStart; + final Color backgroundColorEnd; + + /// The brightness of the app bar's material. Typically this is set along + /// with [backgroundColor], [iconTheme], [textTheme]. + /// + /// Defaults to [ThemeData.primaryColorBrightness]. + final Brightness brightness; + + /// The color, opacity, and size to use for app bar icons. Typically this + /// is set along with [backgroundColor], [brightness], [textTheme]. + /// + /// Defaults to [ThemeData.primaryIconTheme]. + final IconThemeData iconTheme; + + /// The typographic styles to use for text in the app bar. Typically this is + /// set along with [brightness] [backgroundColor], [iconTheme]. + /// + /// Defaults to [ThemeData.primaryTextTheme]. + 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; + + /// 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; + + /// 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. + 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. + final bool pinned; + + /// 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. + /// + /// Snapping only applies when the app bar is floating, not when the appbar + /// appears at the top of its scroll view. + final bool snap; + + @override + _SliverGradientAppBarState createState() => new _SliverGradientAppBarState(); +} + +// This class is only Stateful because it owns the TickerProvider used +// by the floating appbar snap animation (via FloatingHeaderSnapConfiguration). +class _SliverGradientAppBarState extends State + with TickerProviderStateMixin { + FloatingHeaderSnapConfiguration _snapConfiguration; + + void _updateSnapConfiguration() { + if (widget.snap && widget.floating) { + _snapConfiguration = new FloatingHeaderSnapConfiguration( + vsync: this, + curve: Curves.easeOut, + duration: const Duration(milliseconds: 200), + ); + } else { + _snapConfiguration = null; + } + } + + @override + void initState() { + super.initState(); + _updateSnapConfiguration(); + } + + @override + void didUpdateWidget(SliverGradientAppBar oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.snap != oldWidget.snap || widget.floating != oldWidget.floating) + _updateSnapConfiguration(); + } + + @override + Widget build(BuildContext context) { + assert(!widget.primary || debugCheckHasMediaQuery(context)); + final double topPadding = + widget.primary ? MediaQuery.of(context).padding.top : 0.0; + final double collapsedHeight = + (widget.pinned && widget.floating && widget.bottom != null) + ? widget.bottom.preferredSize.height + topPadding + : null; + + return new MediaQuery.removePadding( + context: context, + removeBottom: true, + child: new SliverPersistentHeader( + floating: widget.floating, + pinned: widget.pinned, + delegate: new _SliverGradientAppBarDelegate( + leading: widget.leading, + automaticallyImplyLeading: widget.automaticallyImplyLeading, + title: widget.title, + actions: widget.actions, + flexibleSpace: widget.flexibleSpace, + bottom: widget.bottom, + elevation: widget.elevation, + forceElevated: widget.forceElevated, + backgroundColorStart: widget.backgroundColorStart, + backgroundColorEnd: widget.backgroundColorEnd, + brightness: widget.brightness, + iconTheme: widget.iconTheme, + textTheme: widget.textTheme, + primary: widget.primary, + centerTitle: widget.centerTitle, + titleSpacing: widget.titleSpacing, + expandedHeight: widget.expandedHeight, + collapsedHeight: collapsedHeight, + topPadding: topPadding, + floating: widget.floating, + pinned: widget.pinned, + snapConfiguration: _snapConfiguration, + ), + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock new file mode 100644 index 0000000..ae230bc --- /dev/null +++ b/pubspec.lock @@ -0,0 +1,377 @@ +# Generated by pub +# See https://www.dartlang.org/tools/pub/glossary#lockfile +packages: + analyzer: + dependency: transitive + description: + name: analyzer + url: "https://pub.dartlang.org" + source: hosted + version: "0.32.4" + args: + dependency: transitive + description: + name: args + url: "https://pub.dartlang.org" + source: hosted + version: "1.5.0" + async: + dependency: transitive + description: + name: async + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.8" + boolean_selector: + dependency: transitive + description: + name: boolean_selector + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + charcode: + dependency: transitive + description: + name: charcode + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.2" + collection: + dependency: transitive + description: + name: collection + url: "https://pub.dartlang.org" + source: hosted + version: "1.14.6" + convert: + dependency: transitive + description: + name: convert + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.2" + crypto: + dependency: transitive + description: + name: crypto + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + csslib: + dependency: transitive + description: + name: csslib + url: "https://pub.dartlang.org" + source: hosted + version: "0.14.4+1" + flutter: + dependency: "direct main" + description: flutter + source: sdk + version: "0.0.0" + flutter_test: + dependency: "direct dev" + description: flutter + source: sdk + version: "0.0.0" + front_end: + dependency: transitive + description: + name: front_end + url: "https://pub.dartlang.org" + source: hosted + version: "0.1.4" + glob: + dependency: transitive + description: + name: glob + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.7" + html: + dependency: transitive + description: + name: html + url: "https://pub.dartlang.org" + source: hosted + version: "0.13.3+2" + http: + dependency: transitive + description: + name: http + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.3+17" + http_multi_server: + dependency: transitive + description: + name: http_multi_server + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.5" + http_parser: + dependency: transitive + description: + name: http_parser + url: "https://pub.dartlang.org" + source: hosted + version: "3.1.3" + io: + dependency: transitive + description: + name: io + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.3" + js: + dependency: transitive + description: + name: js + url: "https://pub.dartlang.org" + source: hosted + version: "0.6.1+1" + json_rpc_2: + dependency: transitive + description: + name: json_rpc_2 + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.9" + kernel: + dependency: transitive + description: + name: kernel + url: "https://pub.dartlang.org" + source: hosted + version: "0.3.4" + logging: + dependency: transitive + description: + name: logging + url: "https://pub.dartlang.org" + source: hosted + version: "0.11.3+2" + matcher: + dependency: transitive + description: + name: matcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.2" + meta: + dependency: transitive + description: + name: meta + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.5" + mime: + dependency: transitive + description: + name: mime + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.6+2" + multi_server_socket: + dependency: transitive + description: + name: multi_server_socket + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.2" + node_preamble: + dependency: transitive + description: + name: node_preamble + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.4" + package_config: + dependency: transitive + description: + name: package_config + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.5" + package_resolver: + dependency: transitive + description: + name: package_resolver + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + path: + dependency: transitive + description: + name: path + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.2" + plugin: + dependency: transitive + description: + name: plugin + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.0+3" + pool: + dependency: transitive + description: + name: pool + url: "https://pub.dartlang.org" + source: hosted + version: "1.3.6" + pub_semver: + dependency: transitive + description: + name: pub_semver + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.2" + quiver: + dependency: transitive + description: + name: quiver + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.0+1" + shelf: + dependency: transitive + description: + name: shelf + url: "https://pub.dartlang.org" + source: hosted + version: "0.7.3+3" + shelf_packages_handler: + dependency: transitive + description: + name: shelf_packages_handler + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.4" + shelf_static: + dependency: transitive + description: + name: shelf_static + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.8" + shelf_web_socket: + dependency: transitive + description: + name: shelf_web_socket + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.2+3" + sky_engine: + dependency: transitive + description: flutter + source: sdk + version: "0.0.99" + source_map_stack_trace: + dependency: transitive + description: + name: source_map_stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.5" + source_maps: + dependency: transitive + description: + name: source_maps + url: "https://pub.dartlang.org" + source: hosted + version: "0.10.7" + source_span: + dependency: transitive + description: + name: source_span + url: "https://pub.dartlang.org" + source: hosted + version: "1.4.1" + stack_trace: + dependency: transitive + description: + name: stack_trace + url: "https://pub.dartlang.org" + source: hosted + version: "1.9.3" + stream_channel: + dependency: transitive + description: + name: stream_channel + url: "https://pub.dartlang.org" + source: hosted + version: "1.6.8" + string_scanner: + dependency: transitive + description: + name: string_scanner + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.3" + term_glyph: + dependency: transitive + description: + name: term_glyph + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.1" + test: + dependency: transitive + description: + name: test + url: "https://pub.dartlang.org" + source: hosted + version: "0.12.41" + typed_data: + dependency: transitive + description: + name: typed_data + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.5" + utf: + dependency: transitive + description: + name: utf + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.0+5" + vector_math: + dependency: transitive + description: + name: vector_math + url: "https://pub.dartlang.org" + source: hosted + version: "2.0.6" + vm_service_client: + dependency: transitive + description: + name: vm_service_client + url: "https://pub.dartlang.org" + source: hosted + version: "0.2.6" + watcher: + dependency: transitive + description: + name: watcher + url: "https://pub.dartlang.org" + source: hosted + version: "0.9.7+10" + web_socket_channel: + dependency: transitive + description: + name: web_socket_channel + url: "https://pub.dartlang.org" + source: hosted + version: "1.0.9" + yaml: + dependency: transitive + description: + name: yaml + url: "https://pub.dartlang.org" + source: hosted + version: "2.1.15" +sdks: + dart: ">=2.0.0-dev.62.0 <=2.0.0-dev.63.0.flutter-4c9689c1d2" diff --git a/pubspec.yaml b/pubspec.yaml new file mode 100644 index 0000000..8e20f84 --- /dev/null +++ b/pubspec.yaml @@ -0,0 +1,50 @@ +name: gradient_app_bar +description: Gradient app bar for flutter +version: 0.0.1 +author: +homepage: + +dependencies: + flutter: + sdk: flutter + +dev_dependencies: + flutter_test: + sdk: flutter + +# For information on the generic Dart part of this file, see the +# following page: https://www.dartlang.org/tools/pub/pubspec + +# The following section is specific to Flutter. +flutter: + + # To add assets to your package, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + # + # For details regarding assets in packages, see + # https://flutter.io/assets-and-images/#from-packages + # + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.io/assets-and-images/#resolution-aware. + + # To add custom fonts to your package, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts in packages, see + # https://flutter.io/custom-fonts/#from-packages