mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
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
This commit is contained in:
parent
8f3c498f2d
commit
628884a8a8
@ -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);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user