diff --git a/examples/flutter_gallery/lib/demo/grid_list_demo.dart b/examples/flutter_gallery/lib/demo/grid_list_demo.dart index 893587db03a..9a8a0733ed8 100644 --- a/examples/flutter_gallery/lib/demo/grid_list_demo.dart +++ b/examples/flutter_gallery/lib/demo/grid_list_demo.dart @@ -59,11 +59,9 @@ class GridDemoPhotoItem extends StatelessWidget { appBar: new AppBar( title: new Text(photo.title) ), - body: new Material( - child: new Hero( - tag: photoHeroTag, - child: new Image.asset(photo.assetName, fit: ImageFit.cover) - ) + body: new Hero( + tag: photoHeroTag, + child: new Image.asset(photo.assetName, fit: ImageFit.cover) ) ); } diff --git a/examples/flutter_gallery/lib/demo/pesto_demo.dart b/examples/flutter_gallery/lib/demo/pesto_demo.dart index e1eb2335173..86f686da502 100644 --- a/examples/flutter_gallery/lib/demo/pesto_demo.dart +++ b/examples/flutter_gallery/lib/demo/pesto_demo.dart @@ -325,9 +325,10 @@ class _RecipePageState extends State<_RecipePage> { // adjusts based on the size of the screen. If the recipe sheet touches // the edge of the screen, use a slightly different layout. Widget _buildContainer(BuildContext context) { - bool isFavorite = favoriteRecipes.contains(config.recipe); - Size screenSize = MediaQuery.of(context).size; - bool fullWidth = (screenSize.width < _kRecipePageMaxWidth); + final bool isFavorite = favoriteRecipes.contains(config.recipe); + final Size screenSize = MediaQuery.of(context).size; + final bool fullWidth = (screenSize.width < _kRecipePageMaxWidth); + final double appBarHeight = _getAppBarHeight(context); const double fabHalfSize = 28.0; // TODO(mpcomplete): needs to adapt to screen size return new Stack( children: [ @@ -335,6 +336,7 @@ class _RecipePageState extends State<_RecipePage> { top: 0.0, left: 0.0, right: 0.0, + height: appBarHeight + fabHalfSize, child: new Hero( tag: config.recipe.imagePath, child: new Image.asset( @@ -346,7 +348,7 @@ class _RecipePageState extends State<_RecipePage> { new ScrollableViewport( child: new RepaintBoundary( child: new Padding( - padding: new EdgeInsets.only(top: _getAppBarHeight(context)), + padding: new EdgeInsets.only(top: appBarHeight), child: new Stack( children: [ new Padding( diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index e738a8d67bb..bed17891de8 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart @@ -14,6 +14,8 @@ import 'tabs.dart'; import 'theme.dart'; import 'typography.dart'; +final Object _kDefaultHeroTag = new Object(); + /// A widget that can appear at the bottom of an [AppBar]. The [Scaffold] uses /// the bottom widget's [bottomHeight] to handle layout for /// [AppBarBehavior.scroll] and [AppBarBehavior.under]. @@ -73,6 +75,7 @@ class AppBar extends StatelessWidget { this.textTheme, this.padding: EdgeInsets.zero, this.centerTitle, + this.heroTag, double expandedHeight, double collapsedHeight }) : _expandedHeight = expandedHeight, @@ -153,6 +156,11 @@ class AppBar extends StatelessWidget { /// Defaults to being adapted to the current [TargetPlatform]. final bool centerTitle; + /// The tag to apply to the app bar's [Hero] widget. + /// + /// Defaults to a tag that matches other app bars. + final Object heroTag; + final double _expandedHeight; final double _collapsedHeight; @@ -169,6 +177,7 @@ class AppBar extends StatelessWidget { Brightness brightness, TextTheme textTheme, EdgeInsets padding, + Object heroTag, double expandedHeight, double collapsedHeight }) { @@ -185,6 +194,7 @@ class AppBar extends StatelessWidget { iconTheme: iconTheme ?? this.iconTheme, textTheme: textTheme ?? this.textTheme, padding: padding ?? this.padding, + heroTag: heroTag ?? this.heroTag, expandedHeight: expandedHeight ?? this._expandedHeight, collapsedHeight: collapsedHeight ?? this._collapsedHeight ); @@ -365,13 +375,17 @@ class AppBar extends StatelessWidget { ); } - appBar = new Material( - color: backgroundColor ?? themeData.primaryColor, - elevation: elevation, - child: appBar + return new Hero( + tag: heroTag ?? _kDefaultHeroTag, + child: new Material( + color: backgroundColor ?? themeData.primaryColor, + elevation: elevation, + child: new Align( + alignment: FractionalOffset.topCenter, + child: appBar + ) + ) ); - - return appBar; } @override diff --git a/packages/flutter/lib/src/material/flexible_space_bar.dart b/packages/flutter/lib/src/material/flexible_space_bar.dart index 4f896e95014..d700c08a024 100644 --- a/packages/flutter/lib/src/material/flexible_space_bar.dart +++ b/packages/flutter/lib/src/material/flexible_space_bar.dart @@ -6,7 +6,6 @@ import 'dart:math' as math; import 'package:flutter/widgets.dart'; -import 'debug.dart'; import 'constants.dart'; import 'scaffold.dart'; import 'theme.dart'; @@ -58,7 +57,20 @@ class FlexibleSpaceBar extends StatefulWidget { } class _FlexibleSpaceBarState extends State { - Animation _scaffoldAnimation; + final ProxyAnimation _scaffoldAnimation = new ProxyAnimation(); + double _lastAppBarHeight; + + @override + void initState() { + super.initState(); + _scaffoldAnimation.addListener(_handleTick); + } + + @override + void dispose() { + _scaffoldAnimation.removeListener(_handleTick); + super.dispose(); + } void _handleTick() { setState(() { @@ -66,13 +78,6 @@ class _FlexibleSpaceBarState extends State { }); } - @override - void deactivate() { - _scaffoldAnimation?.removeListener(_handleTick); - _scaffoldAnimation = null; - super.deactivate(); - } - bool _getEffectiveCenterTitle(ThemeData theme) { if (config.centerTitle != null) return config.centerTitle; @@ -88,16 +93,18 @@ class _FlexibleSpaceBarState extends State { @override Widget build(BuildContext context) { - assert(debugCheckHasScaffold(context)); final double statusBarHeight = MediaQuery.of(context).padding.top; final ScaffoldState scaffold = Scaffold.of(context); - _scaffoldAnimation ??= scaffold.appBarAnimation..addListener(_handleTick); - final double appBarHeight = scaffold.appBarHeight + statusBarHeight; + if (scaffold != null) { + _scaffoldAnimation.parent ??= scaffold.appBarAnimation; + _lastAppBarHeight = scaffold.appBarHeight; + } + final double appBarHeight = (_lastAppBarHeight ?? kToolBarHeight) + statusBarHeight; final double toolBarHeight = kToolBarHeight + statusBarHeight; final List children = []; // background image - if (config.background != null) { + if (config.background != null && scaffold != null) { final double fadeStart = (appBarHeight - toolBarHeight * 2.0) / appBarHeight; final double fadeEnd = (appBarHeight - toolBarHeight) / appBarHeight; final CurvedAnimation opacityCurve = new CurvedAnimation( diff --git a/packages/flutter/lib/src/material/page.dart b/packages/flutter/lib/src/material/page.dart index 6754c5062e7..35939b789d4 100644 --- a/packages/flutter/lib/src/material/page.dart +++ b/packages/flutter/lib/src/material/page.dart @@ -6,6 +6,11 @@ import 'dart:async'; import 'package:flutter/widgets.dart'; +final FractionalOffsetTween _kMaterialPageTransitionTween = new FractionalOffsetTween( + begin: FractionalOffset.bottomLeft, + end: FractionalOffset.topLeft +); + class _MaterialPageTransition extends AnimatedWidget { _MaterialPageTransition({ Key key, @@ -13,28 +18,17 @@ class _MaterialPageTransition extends AnimatedWidget { this.child }) : super( key: key, - animation: new CurvedAnimation(parent: animation, curve: Curves.easeOut) + animation: _kMaterialPageTransitionTween.animate(new CurvedAnimation(parent: animation, curve: Curves.fastOutSlowIn)) ); final Widget child; - final Tween _position = new Tween( - begin: const Point(0.0, 75.0), - end: Point.origin - ); - @override Widget build(BuildContext context) { - Point position = _position.evaluate(animation); - Matrix4 transform = new Matrix4.identity() - ..translate(position.x, position.y); - return new Transform( - transform: transform, - // TODO(ianh): tell the transform to be un-transformed for hit testing - child: new Opacity( - opacity: animation.value, - child: child - ) + // TODO(ianh): tell the transform to be un-transformed for hit testing + return new SlideTransition( + position: animation, + child: child ); } } diff --git a/packages/flutter/lib/src/material/tabs.dart b/packages/flutter/lib/src/material/tabs.dart index 50d7c8b6188..a85f2d8debd 100644 --- a/packages/flutter/lib/src/material/tabs.dart +++ b/packages/flutter/lib/src/material/tabs.dart @@ -779,7 +779,8 @@ class _TabBarState extends ScrollableState> implements TabBarSelect super.initState(); scrollBehavior.isScrollable = config.isScrollable; _initSelection(TabBarSelection.of(context)); - _lastSelectedIndex = _selection.index; + if (_selection != null) + _lastSelectedIndex = _selection.index; } @override @@ -969,7 +970,7 @@ class _TabBarState extends ScrollableState> implements TabBarSelect setState(() { _tabBarSize = tabBarSize; _tabWidths = tabWidths; - _indicatorRect = _tabIndicatorRect(_selection.index); + _indicatorRect = _selection != null ? _tabIndicatorRect(_selection.index) : Rect.zero; _updateScrollBehavior(); }); } @@ -981,7 +982,7 @@ class _TabBarState extends ScrollableState> implements TabBarSelect // render object via our return value. _viewportSize = dimensions.containerSize; _updateScrollBehavior(); - if (config.isScrollable) + if (config.isScrollable && _selection != null) scrollTo(_centeredTabScrollOffset(_selection.index), duration: _kTabBarScroll); return scrollOffsetToPixelDelta(scrollOffset); } diff --git a/packages/flutter/lib/src/widgets/editable.dart b/packages/flutter/lib/src/widgets/editable.dart index 9ce9d295eab..e2f1bf926d7 100644 --- a/packages/flutter/lib/src/widgets/editable.dart +++ b/packages/flutter/lib/src/widgets/editable.dart @@ -419,7 +419,7 @@ class RawInputLineState extends ScrollableState { if (focused) { _selectionOverlay.update(config.value); } else { - _selectionOverlay.hide(); + _selectionOverlay?.hide(); _selectionOverlay = null; } }); diff --git a/packages/flutter/lib/src/widgets/heroes.dart b/packages/flutter/lib/src/widgets/heroes.dart index a564dea82a8..c3ac4303bc5 100644 --- a/packages/flutter/lib/src/widgets/heroes.dart +++ b/packages/flutter/lib/src/widgets/heroes.dart @@ -554,7 +554,7 @@ class HeroController extends NavigatorObserver { _to.offstage = false; Animation animation = _animation; - Curve curve = Curves.ease; + Curve curve = Curves.fastOutSlowIn; if (animation.status == AnimationStatus.reverse) { animation = new ReverseAnimation(animation); curve = new Interval(animation.value, 1.0, curve: curve);