diff --git a/packages/flutter/lib/src/material/flexible_space_bar.dart b/packages/flutter/lib/src/material/flexible_space_bar.dart index 50440df0d5..368b55a69a 100644 --- a/packages/flutter/lib/src/material/flexible_space_bar.dart +++ b/packages/flutter/lib/src/material/flexible_space_bar.dart @@ -10,6 +10,18 @@ import 'package:flutter/widgets.dart'; import 'constants.dart'; import 'theme.dart'; +/// The collapsing effect while the space bar expands or collapses. +enum CollapseMode { + /// The background widget will scroll in a parallax fashion. + parallax, + + /// The background widget pin in place until it reaches the min extent. + pin, + + /// The background widget will act as normal with no collapsing effect. + none, +} + /// The part of a material design [AppBar] that expands and collapses. /// /// Most commonly used in in the [SliverAppBar.flexibleSpace] field, a flexible @@ -34,8 +46,10 @@ class FlexibleSpaceBar extends StatefulWidget { Key key, this.title, this.background, - this.centerTitle - }) : super(key: key); + this.centerTitle, + this.collapseMode = CollapseMode.parallax + }) : assert(collapseMode != null), + super(key: key); /// The primary contents of the flexible space bar when expanded. /// @@ -52,6 +66,11 @@ class FlexibleSpaceBar extends StatefulWidget { /// Defaults to being adapted to the current [TargetPlatform]. final bool centerTitle; + /// Collapse effect while scrolling. + /// + /// Defaults to [CollapseMode.parallax]. + final CollapseMode collapseMode; + /// Wraps a widget that contains an [AppBar] to convey sizing information down /// to the [FlexibleSpaceBar]. /// @@ -106,6 +125,19 @@ class _FlexibleSpaceBarState extends State { 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 -new Tween(begin: 0.0, end: deltaExtent / 4.0).lerp(t); + } + return null; + } + @override Widget build(BuildContext context) { final _FlexibleSpaceBarSettings settings = context.inheritFromWidgetOfExactType(_FlexibleSpaceBarSettings); @@ -125,10 +157,9 @@ class _FlexibleSpaceBarState extends State { const double fadeEnd = 1.0; assert(fadeStart <= fadeEnd); final double opacity = 1.0 - new Interval(fadeStart, fadeEnd).transform(t); - final double parallax = new Tween(begin: 0.0, end: deltaExtent / 4.0).lerp(t); if (opacity > 0.0) { children.add(new Positioned( - top: -parallax, + top: _getCollapsePadding(t, settings), left: 0.0, right: 0.0, height: settings.maxExtent, diff --git a/packages/flutter/test/material/flexible_space_bar_collapse_mode_test.dart b/packages/flutter/test/material/flexible_space_bar_collapse_mode_test.dart new file mode 100644 index 0000000000..e8ded69085 --- /dev/null +++ b/packages/flutter/test/material/flexible_space_bar_collapse_mode_test.dart @@ -0,0 +1,252 @@ +// 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 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +final Key blockKey = new UniqueKey(); +const double expandedAppbarHeight = 250.0; +final Key appbarContainerKey = new UniqueKey(); + +void main() { + testWidgets('FlexibleSpaceBar collapse mode none on Android', (WidgetTester tester) async { + await tester.pumpWidget( + new MaterialApp( + theme: new ThemeData(platform: TargetPlatform.android), + home: new Scaffold( + body: new CustomScrollView( + key: blockKey, + slivers: [ + new SliverAppBar( + expandedHeight: expandedAppbarHeight, + pinned: true, + flexibleSpace: new FlexibleSpaceBar( + background: new Container( + key: appbarContainerKey, + ), + collapseMode: CollapseMode.none, + ), + ), + new SliverToBoxAdapter( + child: new Container( + height: 10000.0, + ), + ), + ], + ), + ), + ), + ); + + final Finder appbarContainer = find.byKey(appbarContainerKey); + final Offset topBeforeScroll = tester.getTopLeft(appbarContainer); + await slowDrag(tester, blockKey, const Offset(0.0, -100.0)); + final Offset topAfterScroll = tester.getTopLeft(appbarContainer); + + expect(topBeforeScroll.dy, equals(0.0)); + expect(topAfterScroll.dy, equals(0.0)); + }); + + testWidgets('FlexibleSpaceBar collapse mode none on IOS', (WidgetTester tester) async { + await tester.pumpWidget( + new MaterialApp( + theme: new ThemeData(platform: TargetPlatform.iOS), + home: new Scaffold( + body: new CustomScrollView( + key: blockKey, + slivers: [ + new SliverAppBar( + expandedHeight: expandedAppbarHeight, + pinned: true, + flexibleSpace: new FlexibleSpaceBar( + background: new Container( + key: appbarContainerKey, + ), + collapseMode: CollapseMode.none, + ), + ), + new SliverToBoxAdapter( + child: new Container( + height: 10000.0, + ), + ), + ], + ), + ), + ), + ); + + final Finder appbarContainer = find.byKey(appbarContainerKey); + final Offset topBeforeScroll = tester.getTopLeft(appbarContainer); + await slowDrag(tester, blockKey, const Offset(0.0, -100.0)); + final Offset topAfterScroll = tester.getTopLeft(appbarContainer); + + expect(topBeforeScroll.dy, equals(0.0)); + expect(topAfterScroll.dy, equals(0.0)); + }); + + testWidgets('FlexibleSpaceBar collapse mode pin on Android', (WidgetTester tester) async { + await tester.pumpWidget( + new MaterialApp( + theme: new ThemeData(platform: TargetPlatform.android), + home: new Scaffold( + body: new CustomScrollView( + key: blockKey, + slivers: [ + new SliverAppBar( + expandedHeight: expandedAppbarHeight, + pinned: true, + flexibleSpace: new FlexibleSpaceBar( + background: new Container( + key: appbarContainerKey, + ), + collapseMode: CollapseMode.pin, + ), + ), + new SliverToBoxAdapter( + child: new Container( + height: 10000.0, + ), + ), + ], + ), + ), + ), + ); + + final Finder appbarContainer = find.byKey(appbarContainerKey); + final Offset topBeforeScroll = tester.getTopLeft(appbarContainer); + await slowDrag(tester, blockKey, const Offset(0.0, -100.0)); + final Offset topAfterScroll = tester.getTopLeft(appbarContainer); + + expect(topBeforeScroll.dy, equals(0.0)); + expect(topAfterScroll.dy, equals(-100.0)); + }); + + testWidgets('FlexibleSpaceBar collapse mode pin on IOS', (WidgetTester tester) async { + await tester.pumpWidget( + new MaterialApp( + theme: new ThemeData(platform: TargetPlatform.iOS), + home: new Scaffold( + body: new CustomScrollView( + key: blockKey, + slivers: [ + new SliverAppBar( + expandedHeight: expandedAppbarHeight, + pinned: true, + flexibleSpace: new FlexibleSpaceBar( + background: new Container( + key: appbarContainerKey, + ), + collapseMode: CollapseMode.pin, + ), + ), + new SliverToBoxAdapter( + child: new Container( + height: 10000.0, + ), + ), + ], + ), + ), + ), + ); + + final Finder appbarContainer = find.byKey(appbarContainerKey); + final Offset topBeforeScroll = tester.getTopLeft(appbarContainer); + await slowDrag(tester, blockKey, const Offset(0.0, -100.0)); + final Offset topAfterScroll = tester.getTopLeft(appbarContainer); + + expect(topBeforeScroll.dy, equals(0.0)); + expect(topAfterScroll.dy, equals(-100.0)); + }); + + + + testWidgets('FlexibleSpaceBar collapse mode parallax on Android', (WidgetTester tester) async { + await tester.pumpWidget( + new MaterialApp( + theme: new ThemeData(platform: TargetPlatform.android), + home: new Scaffold( + body: new CustomScrollView( + key: blockKey, + slivers: [ + new SliverAppBar( + expandedHeight: expandedAppbarHeight, + pinned: true, + flexibleSpace: new FlexibleSpaceBar( + background: new Container( + key: appbarContainerKey, + ), + collapseMode: CollapseMode.parallax, + ), + ), + new SliverToBoxAdapter( + child: new Container( + height: 10000.0, + ), + ), + ], + ), + ), + ), + ); + + final Finder appbarContainer = find.byKey(appbarContainerKey); + final Offset topBeforeScroll = tester.getTopLeft(appbarContainer); + await slowDrag(tester, blockKey, const Offset(0.0, -100.0)); + final Offset topAfterScroll = tester.getTopLeft(appbarContainer); + + expect(topBeforeScroll.dy, equals(0.0)); + expect(topAfterScroll.dy, lessThan(10.0)); + expect(topAfterScroll.dy, greaterThan(-50.0)); + }); + + testWidgets('FlexibleSpaceBar collapse mode parallax on IOS', (WidgetTester tester) async { + await tester.pumpWidget( + new MaterialApp( + theme: new ThemeData(platform: TargetPlatform.iOS), + home: new Scaffold( + body: new CustomScrollView( + key: blockKey, + slivers: [ + new SliverAppBar( + expandedHeight: expandedAppbarHeight, + pinned: true, + flexibleSpace: new FlexibleSpaceBar( + background: new Container( + key: appbarContainerKey, + ), + collapseMode: CollapseMode.parallax, + ), + ), + new SliverToBoxAdapter( + child: new Container( + height: 10000.0, + ), + ), + ], + ), + ), + ), + ); + + final Finder appbarContainer = find.byKey(appbarContainerKey); + final Offset topBeforeScroll = tester.getTopLeft(appbarContainer); + await slowDrag(tester, blockKey, const Offset(0.0, -100.0)); + final Offset topAfterScroll = tester.getTopLeft(appbarContainer); + + expect(topBeforeScroll.dy, equals(0.0)); + expect(topAfterScroll.dy, lessThan(10.0)); + expect(topAfterScroll.dy, greaterThan(-50.0)); + }); +} + +Future slowDrag(WidgetTester tester, Key widget, Offset offset) async { + final Offset target = tester.getCenter(find.byKey(widget)); + final TestGesture gesture = await tester.startGesture(target); + await gesture.moveBy(offset); + await tester.pump(const Duration(milliseconds: 10)); + await gesture.up(); +} \ No newline at end of file