| // Copyright 2016 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:math' as math; |
| |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/widgets.dart'; |
| |
| import 'constants.dart'; |
| import 'theme.dart'; |
| |
| /// The part of a material design [AppBar] that expands and collapses. |
| /// |
| /// Most commonly used in in the [SliverAppBar.flexibleSpace] field, a flexible |
| /// space bar expands and contracts as the app scrolls so that the [AppBar] |
| /// reaches from the top of the app to the top of the scrolling contents of the |
| /// app. |
| /// |
| /// The widget that sizes the [AppBar] must wrap it in the widget returned by |
| /// [FlexibleSpaceBar.createSettings], to convey sizing information down to the |
| /// [FlexibleSpaceBar]. |
| /// |
| /// See also: |
| /// |
| /// * [SliverAppBar], which implements the expanding and contracting. |
| /// * [AppBar], which is used by [SliverAppBar]. |
| /// * <https://material.google.com/patterns/scrolling-techniques.html> |
| 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 |
| }) : 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. |
| /// |
| /// Defaults to being adapted to the current [TargetPlatform]. |
| final bool centerTitle; |
| |
| /// Wraps a widget that contains an [AppBar] to convey sizing information down |
| /// to the [FlexibleSpaceBar]. |
| /// |
| /// Used by [Scaffold] and [SliverAppBar]. |
| static Widget createSettings({ |
| double toolbarOpacity, |
| double minExtent, |
| double maxExtent, |
| @required double currentExtent, |
| @required Widget child, |
| }) { |
| assert(currentExtent != null); |
| return new _FlexibleSpaceBarSettings( |
| toolbarOpacity: toolbarOpacity ?? 1.0, |
| minExtent: minExtent ?? currentExtent, |
| maxExtent: maxExtent ?? currentExtent, |
| currentExtent: currentExtent, |
| child: child, |
| ); |
| } |
| |
| @override |
| _FlexibleSpaceBarState createState() => new _FlexibleSpaceBarState(); |
| } |
| |
| class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> { |
| bool _getEffectiveCenterTitle(ThemeData theme) { |
| if (widget.centerTitle != null) |
| return widget.centerTitle; |
| assert(theme.platform != null); |
| switch (theme.platform) { |
| case TargetPlatform.android: |
| case TargetPlatform.fuchsia: |
| return false; |
| case TargetPlatform.iOS: |
| return true; |
| } |
| 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; |
| } |
| |
| @override |
| Widget build(BuildContext context) { |
| final _FlexibleSpaceBarSettings settings = context.inheritFromWidgetOfExactType(_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); |
| |
| // background image |
| if (widget.background != null) { |
| final double fadeStart = math.max(0.0, 1.0 - kToolbarHeight / deltaExtent); |
| final double fadeEnd = 1.0; |
| assert(fadeStart <= fadeEnd); |
| final double opacity = 1.0 - new Interval(fadeStart, fadeEnd).transform(t); |
| final double parallax = new Tween<double>(begin: 0.0, end: deltaExtent / 4.0).lerp(t); |
| if (opacity > 0.0) { |
| children.add(new Positioned( |
| top: -parallax, |
| left: 0.0, |
| right: 0.0, |
| height: settings.maxExtent, |
| child: new Opacity( |
| opacity: opacity, |
| child: widget.background |
| ) |
| )); |
| } |
| } |
| |
| if (widget.title != null) { |
| final ThemeData theme = Theme.of(context); |
| 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 double scaleValue = new Tween<double>(begin: 1.5, end: 1.0).lerp(t); |
| final Matrix4 scaleTransform = new Matrix4.identity() |
| ..scale(scaleValue, scaleValue, 1.0); |
| final Alignment titleAlignment = _getTitleAlignment(effectiveCenterTitle); |
| children.add(new Container( |
| padding: new EdgeInsetsDirectional.only( |
| start: effectiveCenterTitle ? 0.0 : 72.0, |
| bottom: 16.0 |
| ), |
| child: new Transform( |
| alignment: titleAlignment, |
| transform: scaleTransform, |
| child: new Align( |
| alignment: titleAlignment, |
| child: new DefaultTextStyle(style: titleStyle, child: widget.title) |
| ) |
| ) |
| )); |
| } |
| } |
| |
| return new ClipRect(child: new Stack(children: children)); |
| } |
| } |
| |
| class _FlexibleSpaceBarSettings extends InheritedWidget { |
| const _FlexibleSpaceBarSettings({ |
| Key key, |
| this.toolbarOpacity, |
| this.minExtent, |
| this.maxExtent, |
| this.currentExtent, |
| Widget child, |
| }) : super(key: key, child: child); |
| |
| final double toolbarOpacity; |
| final double minExtent; |
| final double maxExtent; |
| final double currentExtent; |
| |
| @override |
| bool updateShouldNotify(_FlexibleSpaceBarSettings oldWidget) { |
| return toolbarOpacity != oldWidget.toolbarOpacity |
| || minExtent != oldWidget.minExtent |
| || maxExtent != oldWidget.maxExtent |
| || currentExtent != oldWidget.currentExtent; |
| } |
| } |