Created
April 15, 2020 14:34
-
-
Save preetjdp/bbebd191769599817770cbd42ec5ca74 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import 'package:flutter/foundation.dart'; | |
| import 'package:flutter/material.dart'; | |
| class FlexibleSpaceBar extends StatefulWidget { | |
| /// Creates a flexible space bar. | |
| /// | |
| /// Most commonly used in the [AppBar.flexibleSpace] field. | |
| const FlexibleSpaceBar({ | |
| Key key, | |
| this.title, | |
| this.background, | |
| this.centerTitle, | |
| this.titlePadding, | |
| this.collapseMode = CollapseMode.parallax, | |
| this.stretchModes = const <StretchMode>[StretchMode.zoomBackground], | |
| }) : assert(collapseMode != null), | |
| super(key: key); | |
| /// The primary contents of the flexible space bar when expanded. | |
| /// | |
| /// Typically a [Text] widget. | |
| final Widget title; | |
| /// Shown behind the [title] when expanded. | |
| /// | |
| /// Typically an [Image] widget with [Image.fit] set to [BoxFit.cover]. | |
| final Widget background; | |
| /// Whether the title should be centered. | |
| /// | |
| /// By default this property is true if the current target platform | |
| /// is [TargetPlatform.iOS] or [TargetPlatform.macOS], false otherwise. | |
| final bool centerTitle; | |
| /// Collapse effect while scrolling. | |
| /// | |
| /// Defaults to [CollapseMode.parallax]. | |
| final CollapseMode collapseMode; | |
| /// Stretch effect while over-scrolling, | |
| /// | |
| /// Defaults to include [StretchMode.zoomBackground]. | |
| final List<StretchMode> stretchModes; | |
| /// Defines how far the [title] is inset from either the widget's | |
| /// bottom-left or its center. | |
| /// | |
| /// Typically this property is used to adjust how far the title is | |
| /// is inset from the bottom-left and it is specified along with | |
| /// [centerTitle] false. | |
| /// | |
| /// By default the value of this property is | |
| /// `EdgeInsetsDirectional.only(start: 72, bottom: 16)` if the title is | |
| /// not centered, `EdgeInsetsDirectional.only(start 0, bottom: 16)` otherwise. | |
| final EdgeInsetsGeometry titlePadding; | |
| /// Wraps a widget that contains an [AppBar] to convey sizing information down | |
| /// to the [FlexibleSpaceBar]. | |
| /// | |
| /// Used by [Scaffold] and [SliverAppBar]. | |
| /// | |
| /// `toolbarOpacity` affects how transparent the text within the toolbar | |
| /// appears. `minExtent` sets the minimum height of the resulting | |
| /// [FlexibleSpaceBar] when fully collapsed. `maxExtent` sets the maximum | |
| /// height of the resulting [FlexibleSpaceBar] when fully expanded. | |
| /// `currentExtent` sets the scale of the [FlexibleSpaceBar.background] and | |
| /// [FlexibleSpaceBar.title] widgets of [FlexibleSpaceBar] upon | |
| /// initialization. | |
| /// | |
| /// See also: | |
| /// | |
| /// * [FlexibleSpaceBarSettings] which creates a settings object that can be | |
| /// used to specify these settings to a [FlexibleSpaceBar]. | |
| static Widget createSettings({ | |
| double toolbarOpacity, | |
| double minExtent, | |
| double maxExtent, | |
| @required double currentExtent, | |
| @required Widget child, | |
| }) { | |
| assert(currentExtent != null); | |
| return FlexibleSpaceBarSettings( | |
| toolbarOpacity: toolbarOpacity ?? 1.0, | |
| minExtent: minExtent ?? currentExtent, | |
| maxExtent: maxExtent ?? currentExtent, | |
| currentExtent: currentExtent, | |
| child: child, | |
| ); | |
| } | |
| @override | |
| _FlexibleSpaceBarState createState() => _FlexibleSpaceBarState(); | |
| } | |
| class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> { | |
| bool _getEffectiveCenterTitle(ThemeData theme) { | |
| if (widget.centerTitle != null) return widget.centerTitle; | |
| assert(theme.platform != null); | |
| return false; | |
| // switch (theme.platform) { | |
| // case TargetPlatform.android: | |
| // case TargetPlatform.fuchsia: | |
| // return false; | |
| // case TargetPlatform.iOS: | |
| // case TargetPlatform.macOS: | |
| // return false; | |
| // } | |
| return null; | |
| } | |
| Alignment _getTitleAlignment(bool effectiveCenterTitle) { | |
| if (effectiveCenterTitle) return Alignment.bottomCenter; | |
| final TextDirection textDirection = Directionality.of(context); | |
| assert(textDirection != null); | |
| switch (textDirection) { | |
| case TextDirection.rtl: | |
| return Alignment.bottomRight; | |
| case TextDirection.ltr: | |
| return Alignment.bottomLeft; | |
| } | |
| return null; | |
| } | |
| double _getCollapsePadding(double t, FlexibleSpaceBarSettings settings) { | |
| switch (widget.collapseMode) { | |
| case CollapseMode.pin: | |
| return -(settings.maxExtent - settings.currentExtent); | |
| case CollapseMode.none: | |
| return 0.0; | |
| case CollapseMode.parallax: | |
| final double deltaExtent = settings.maxExtent - settings.minExtent; | |
| return -Tween<double>(begin: 0.0, end: deltaExtent / 4.0).transform(t); | |
| } | |
| return null; | |
| } | |
| @override | |
| Widget build(BuildContext context) { | |
| final ModalRoute<dynamic> parentRoute = ModalRoute.of(context); | |
| final bool canPop = parentRoute?.canPop ?? false; | |
| final double xExtent = canPop ? 30 : 0; | |
| return LayoutBuilder( | |
| builder: (BuildContext context, BoxConstraints constraints) { | |
| final FlexibleSpaceBarSettings settings = context | |
| .dependOnInheritedWidgetOfExactType<FlexibleSpaceBarSettings>(); | |
| assert( | |
| settings != null, | |
| 'A FlexibleSpaceBar must be wrapped in the widget returned by FlexibleSpaceBar.createSettings().', | |
| ); | |
| final List<Widget> children = <Widget>[]; | |
| final double deltaExtent = settings.maxExtent - settings.minExtent; | |
| // 0.0 -> Expanded | |
| // 1.0 -> Collapsed to toolbar | |
| final double t = | |
| (1.0 - (settings.currentExtent - settings.minExtent) / deltaExtent) | |
| .clamp(0.0, 1.0) as double; | |
| // background | |
| if (widget.background != null) { | |
| final double fadeStart = | |
| math.max(0.0, 1.0 - kToolbarHeight / deltaExtent); | |
| const double fadeEnd = 1.0; | |
| assert(fadeStart <= fadeEnd); | |
| final double opacity = 1.0 - Interval(fadeStart, fadeEnd).transform(t); | |
| if (opacity > 0.0) { | |
| double height = settings.maxExtent; | |
| // StretchMode.zoomBackground | |
| if (widget.stretchModes.contains(StretchMode.zoomBackground) && | |
| constraints.maxHeight > height) { | |
| height = constraints.maxHeight; | |
| } | |
| children.add(Positioned( | |
| top: _getCollapsePadding(t, settings), | |
| left: 0.0, | |
| right: 0.0, | |
| height: height, | |
| child: Opacity( | |
| opacity: opacity, | |
| child: widget.background, | |
| ), | |
| )); | |
| // StretchMode.blurBackground | |
| if (widget.stretchModes.contains(StretchMode.blurBackground) && | |
| constraints.maxHeight > settings.maxExtent) { | |
| final double blurAmount = | |
| (constraints.maxHeight - settings.maxExtent) / 10; | |
| children.add(Positioned.fill( | |
| child: BackdropFilter( | |
| child: Container( | |
| color: Colors.transparent, | |
| ), | |
| filter: ui.ImageFilter.blur( | |
| sigmaX: blurAmount, | |
| sigmaY: blurAmount, | |
| )))); | |
| } | |
| } | |
| } | |
| // title | |
| if (widget.title != null) { | |
| final ThemeData theme = Theme.of(context); | |
| Widget title; | |
| switch (theme.platform) { | |
| case TargetPlatform.iOS: | |
| case TargetPlatform.macOS: | |
| title = widget.title; | |
| break; | |
| case TargetPlatform.fuchsia: | |
| case TargetPlatform.android: | |
| title = Semantics( | |
| namesRoute: true, | |
| child: widget.title, | |
| ); | |
| break; | |
| } | |
| // StretchMode.fadeTitle | |
| if (widget.stretchModes.contains(StretchMode.fadeTitle) && | |
| constraints.maxHeight > settings.maxExtent) { | |
| final double stretchOpacity = 1 - | |
| (((constraints.maxHeight - settings.maxExtent) / 100) | |
| .clamp(0.0, 1.0) as double); | |
| title = Opacity( | |
| opacity: stretchOpacity, | |
| child: title, | |
| ); | |
| } | |
| final double opacity = settings.toolbarOpacity; | |
| if (opacity > 0.0) { | |
| TextStyle titleStyle = theme.primaryTextTheme.title; | |
| titleStyle = | |
| titleStyle.copyWith(color: titleStyle.color.withOpacity(opacity)); | |
| final bool effectiveCenterTitle = _getEffectiveCenterTitle(theme); | |
| final EdgeInsetsGeometry padding = widget.titlePadding ?? | |
| EdgeInsetsDirectional.only( | |
| start: effectiveCenterTitle ? 0.0 : 72.0, | |
| bottom: 16.0, | |
| ); | |
| final double scaleValue = | |
| Tween<double>(begin: 1.5, end: 1.0).transform(t); | |
| final double xScaleValue = | |
| Tween<double>(begin: 0, end: xExtent).transform(t); | |
| final Matrix4 scaleTransform = Matrix4.identity() | |
| ..scale(scaleValue, scaleValue, 1.0) | |
| ..translate(xScaleValue); | |
| final Alignment titleAlignment = | |
| _getTitleAlignment(effectiveCenterTitle); | |
| children.add(Container( | |
| padding: padding, | |
| child: Transform( | |
| alignment: titleAlignment, | |
| transform: scaleTransform, | |
| child: Align( | |
| alignment: titleAlignment, | |
| child: DefaultTextStyle( | |
| style: titleStyle, | |
| child: title, | |
| ), | |
| ), | |
| ), | |
| )); | |
| } | |
| } | |
| return ClipRect(child: Stack(children: children)); | |
| }); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment