flexible_space_bar.dart 5.22 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13
// 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';

import 'debug.dart';
import 'constants.dart';
import 'scaffold.dart';
import 'theme.dart';

14 15 16 17 18 19 20 21 22 23 24 25 26 27
/// 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:
///  * [AppBar]
///  * [Scaffold]
///  * <https://www.google.com/design/spec/patterns/scrolling-techniques.html>
28
class FlexibleSpaceBar extends StatefulWidget {
29 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.
  FlexibleSpaceBar({ Key key, this.title, this.background }) : super(key: key);
34

35 36 37
  /// The primary contents of the flexible space bar when expanded.
  ///
  /// Typically a [Text] widget.
38
  final Widget title;
39 40 41 42 43

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

45
  @override
46 47 48 49
  _FlexibleSpaceBarState createState() => new _FlexibleSpaceBarState();
}

class _FlexibleSpaceBarState extends State<FlexibleSpaceBar> {
50 51 52 53 54 55 56 57 58 59 60 61 62 63
  Animation<double> _scaffoldAnimation;

  void _handleTick() {
    setState(() {
      // The animation's state is our build state, and it changed already.
    });
  }

  @override
  void deactivate() {
    _scaffoldAnimation?.removeListener(_handleTick);
    _scaffoldAnimation = null;
  }

64
  @override
65 66
  Widget build(BuildContext context) {
    assert(debugCheckHasScaffold(context));
67
    final double statusBarHeight = MediaQuery.of(context).padding.top;
68 69 70
    final ScaffoldState scaffold = Scaffold.of(context);
    _scaffoldAnimation ??= scaffold.appBarAnimation..addListener(_handleTick);
    final double appBarHeight = scaffold.appBarHeight + statusBarHeight;
71
    final double toolBarHeight = kToolBarHeight + statusBarHeight;
72 73 74
    final List<Widget> children = <Widget>[];

    // background image
75
    if (config.background != null) {
76 77 78
      final double fadeStart = (appBarHeight - toolBarHeight * 2.0) / appBarHeight;
      final double fadeEnd = (appBarHeight - toolBarHeight) / appBarHeight;
      final CurvedAnimation opacityCurve = new CurvedAnimation(
79
        parent: _scaffoldAnimation,
80 81
        curve: new Interval(math.max(0.0, fadeStart), math.min(fadeEnd, 1.0))
      );
82
      final double parallax = new Tween<double>(begin: 0.0, end: appBarHeight / 4.0).evaluate(_scaffoldAnimation);
83 84 85 86 87 88 89 90 91 92 93 94
      final double opacity = new Tween<double>(begin: 1.0, end: 0.0).evaluate(opacityCurve);
      if (opacity > 0.0) {
        children.add(new Positioned(
          top: -parallax,
          left: 0.0,
          right: 0.0,
          child: new Opacity(
            opacity: opacity,
            child: new SizedBox(
              height: appBarHeight + statusBarHeight,
              child: config.background
            )
95
          )
96 97
        ));
      }
98 99 100 101 102 103 104
    }

    // title
    if (config.title != null) {
      final double fadeStart = (appBarHeight - toolBarHeight) / appBarHeight;
      final double fadeEnd = (appBarHeight - toolBarHeight / 2.0) / appBarHeight;
      final CurvedAnimation opacityCurve = new CurvedAnimation(
105
        parent: _scaffoldAnimation,
106 107
        curve: new Interval(fadeStart, fadeEnd)
      );
108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134
      final int alpha = new Tween<double>(begin: 255.0, end: 0.0).evaluate(opacityCurve).toInt();
      if (alpha > 0) {
        TextStyle titleStyle = Theme.of(context).primaryTextTheme.title;
        titleStyle = titleStyle.copyWith(
          color: titleStyle.color.withAlpha(alpha)
        );
        final double yAlignStart = 1.0;
        final double yAlignEnd = (statusBarHeight + kToolBarHeight / 2.0) / toolBarHeight;
        final double scaleAndAlignEnd = (appBarHeight - toolBarHeight) / appBarHeight;
        final CurvedAnimation scaleAndAlignCurve = new CurvedAnimation(
          parent: _scaffoldAnimation,
          curve: new Interval(0.0, scaleAndAlignEnd)
        );
        children.add(new Padding(
          padding: const EdgeInsets.only(left: 72.0, bottom: 14.0),
          child: new Align(
            alignment: new Tween<FractionalOffset>(
              begin: new FractionalOffset(0.0, yAlignStart),
              end: new FractionalOffset(0.0, yAlignEnd)
            ).evaluate(scaleAndAlignCurve),
            child: new ScaleTransition(
              alignment: FractionalOffset.bottomLeft,
              scale: new Tween<double>(begin: 1.5, end: 1.0).animate(scaleAndAlignCurve),
              child: new Align(
                alignment: new FractionalOffset(0.0, 1.0),
                child: new DefaultTextStyle(style: titleStyle, child: config.title)
              )
135 136
            )
          )
137 138
        ));
      }
139 140 141 142 143
    }

    return new ClipRect(child: new Stack(children: children));
  }
}