flexible_space_bar.dart 4.87 KB
Newer Older
1 2 3 4 5 6 7 8
// 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/widgets.dart';

9
import 'app_bar.dart';
10 11 12 13
import 'constants.dart';
import 'scaffold.dart';
import 'theme.dart';

14 15 16 17 18 19 20 21 22 23 24
/// The part of a material design [AppBar] that expands and collapses.
///
/// Most commonly used in in the [AppBar.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.
///
/// Requires one of its ancestors to be a [Scaffold] widget because the
/// [Scaffold] coordinates the scrolling effect between the flexible space and
/// its body.
///
/// See also:
Ian Hickson's avatar
Ian Hickson committed
25
///
26 27
///  * [AppBar]
///  * [Scaffold]
28
///  * <https://material.google.com/patterns/scrolling-techniques.html>
29
class FlexibleSpaceBar extends StatefulWidget {
30 31 32 33
  /// Creates a flexible space bar.
  ///
  /// Most commonly used in the [AppBar.flexibleSpace] field. Requires one of
  /// its ancestors to be a [Scaffold] widget.
34 35 36 37 38 39
  FlexibleSpaceBar({
    Key key,
    this.title,
    this.background,
    this.centerTitle
  }) : super(key: key);
40

41 42 43
  /// The primary contents of the flexible space bar when expanded.
  ///
  /// Typically a [Text] widget.
44
  final Widget title;
45 46 47 48 49

  /// Shown behind the [title] when expanded.
  ///
  /// Typically an [AssetImage] widget with [AssetImage.fit] set to [ImageFit.cover].
  final Widget background;
50

51 52 53 54 55
  /// Whether the title should be centered.
  ///
  /// Defaults to being adapted to the current [TargetPlatform].
  final bool centerTitle;

56
  @override
57 58 59 60
  _FlexibleSpaceBarState createState() => new _FlexibleSpaceBarState();
}

class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
61 62 63 64 65 66
  bool _getEffectiveCenterTitle(ThemeData theme) {
    if (config.centerTitle != null)
      return config.centerTitle;
    assert(theme.platform != null);
    switch (theme.platform) {
      case TargetPlatform.android:
67
      case TargetPlatform.fuchsia:
68 69 70 71
        return false;
      case TargetPlatform.iOS:
        return true;
    }
72
    return null;
73 74
  }

75 76
  Widget _buildContent(BuildContext context, BoxConstraints constraints) {
    final Size size = constraints.biggest;
77
    final double statusBarHeight = MediaQuery.of(context).padding.top;
78 79 80

    final double currentHeight = size.height;
    final double maxHeight = statusBarHeight + AppBar.getExpandedHeightFor(context);
81
    final double minHeight = statusBarHeight + kToolbarHeight;
82 83 84 85 86 87
    final double deltaHeight = maxHeight - minHeight;

    // 0.0 -> Expanded
    // 1.0 -> Collapsed to toolbar
    final double t = (1.0 - (currentHeight - minHeight) / deltaHeight).clamp(0.0, 1.0);

88 89 90
    final List<Widget> children = <Widget>[];

    // background image
91
    if (config.background != null) {
92
      final double fadeStart = math.max(0.0, 1.0 - kToolbarHeight / deltaHeight);
93
      final double fadeEnd = 1.0;
94
      assert(fadeStart <= fadeEnd);
95 96
      final double opacity = 1.0 - new Interval(fadeStart, fadeEnd).transform(t);
      final double parallax = new Tween<double>(begin: 0.0, end: deltaHeight / 4.0).lerp(t);
97 98 99 100 101
      if (opacity > 0.0) {
        children.add(new Positioned(
          top: -parallax,
          left: 0.0,
          right: 0.0,
102
          height: maxHeight,
103 104
          child: new Opacity(
            opacity: opacity,
105
            child: config.background
106
          )
107 108
        ));
      }
109 110 111
    }

    if (config.title != null) {
112
      final ThemeData theme = Theme.of(context);
113
      final double opacity = (1.0 - (minHeight - currentHeight) / (kToolbarHeight - statusBarHeight)).clamp(0.0, 1.0);
114
      if (opacity > 0.0) {
115
        TextStyle titleStyle = theme.primaryTextTheme.title;
116
        titleStyle = titleStyle.copyWith(
117
          color: titleStyle.color.withOpacity(opacity)
118
        );
119
        final bool effectiveCenterTitle = _getEffectiveCenterTitle(theme);
120 121 122
        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);
123
        final FractionalOffset titleAlignment = effectiveCenterTitle ? FractionalOffset.bottomCenter : FractionalOffset.bottomLeft;
124
        children.add(new Container(
125 126 127 128 129
          padding: new EdgeInsets.only(
            left: effectiveCenterTitle ? 0.0 : 72.0,
            bottom: 16.0
          ),
          child: new Transform(
130
            alignment: titleAlignment,
131
            transform: scaleTransform,
132
            child: new Align(
133
              alignment: titleAlignment,
134
              child: new DefaultTextStyle(style: titleStyle, child: config.title)
135 136
            )
          )
137 138
        ));
      }
139 140 141 142
    }

    return new ClipRect(child: new Stack(children: children));
  }
143 144 145 146 147

  @override
  Widget build(BuildContext context) {
    return new LayoutBuilder(builder: _buildContent);
  }
148
}