Commit 628884a8 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Make AppBar a Hero (#5214)

This patch improves the Post and Shrine transitions by making the AppBar
into a Hero and changing the default MaterialPageTransition. Now the
AppBar transitions smoothly between screens and the
MaterialPageTransition doesn't involve a fade effect.

Also, rejigger the bounds of the image header in Pesto to avoid the
"pop" at the end of the animation by laying out the image header at its
final visual size instead of relying on occlusion to size the image
header.

Fixes #5202
Fixes #5204
parent 8f3c498f
......@@ -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)
)
);
}
......
......@@ -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: <Widget>[
......@@ -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: <Widget>[
new Padding(
......
......@@ -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
......
......@@ -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<FlexibleSpaceBar> {
Animation<double> _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<FlexibleSpaceBar> {
});
}
@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<FlexibleSpaceBar> {
@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<Widget> children = <Widget>[];
// 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(
......
......@@ -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<Point> _position = new Tween<Point>(
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
);
}
}
......
......@@ -779,7 +779,8 @@ class _TabBarState<T> extends ScrollableState<TabBar<T>> 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<T> extends ScrollableState<TabBar<T>> 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<T> extends ScrollableState<TabBar<T>> 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);
}
......
......@@ -419,7 +419,7 @@ class RawInputLineState extends ScrollableState<RawInputLine> {
if (focused) {
_selectionOverlay.update(config.value);
} else {
_selectionOverlay.hide();
_selectionOverlay?.hide();
_selectionOverlay = null;
}
});
......
......@@ -554,7 +554,7 @@ class HeroController extends NavigatorObserver {
_to.offstage = false;
Animation<double> 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);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment