mirror of
https://github.com/flutter/flutter
synced 2024-10-13 19:52:53 +00:00
Make the Hero transition animation configurable (#12215)
This commit is contained in:
parent
fde26cd14c
commit
d3d6198852
|
@ -221,9 +221,13 @@ T _maxBy<T>(Iterable<T> input, _KeyFunc<T> keyFunc) {
|
|||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MaterialRectCenterArcTween], which interpolates a rect along a circular
|
||||
/// arc between the begin and end [Rect]'s centers.
|
||||
/// * [Tween], for a discussion on how to use interpolation objects.
|
||||
/// * [MaterialPointArcTween], the analogue for [Offset] interporation.
|
||||
/// * [RectTween], which does a linear rectangle interpolation.
|
||||
/// * [Hero.createRectTween], which can be used to specify the tween that defines
|
||||
/// a hero's path.
|
||||
class MaterialRectArcTween extends RectTween {
|
||||
/// Creates a [Tween] for animating [Rect]s along a circular arc.
|
||||
///
|
||||
|
@ -323,3 +327,89 @@ class MaterialRectArcTween extends RectTween {
|
|||
return '$runtimeType($begin \u2192 $end; beginArc=$beginArc, endArc=$endArc)';
|
||||
}
|
||||
}
|
||||
|
||||
/// A [Tween] that interpolates a [Rect] by moving it along a circular
|
||||
/// arc from [begin.center] to [end.center] while interpoloting the rectangle's
|
||||
/// width and height.
|
||||
///
|
||||
/// The arc that defines that center of the interpolated rectangle as it morphs
|
||||
/// from [begin] to [end] is a [MaterialPointArcTween].
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [MaterialRectArcTween], A [Tween] that interpolates a [Rect] by having
|
||||
/// its opposite corners follow circular arcs.
|
||||
/// * [Tween], for a discussion on how to use interpolation objects.
|
||||
/// * [MaterialPointArcTween], the analogue for [Offset] interporation.
|
||||
/// * [RectTween], which does a linear rectangle interpolation.
|
||||
/// * [Hero.createRectTween], which can be used to specify the tween that defines
|
||||
/// a hero's path.
|
||||
class MaterialRectCenterArcTween extends RectTween {
|
||||
/// Creates a [Tween] for animating [Rect]s along a circular arc.
|
||||
///
|
||||
/// The [begin] and [end] properties must be non-null before the tween is
|
||||
/// first used, but the arguments can be null if the values are going to be
|
||||
/// filled in later.
|
||||
MaterialRectCenterArcTween({
|
||||
Rect begin,
|
||||
Rect end,
|
||||
}) : super(begin: begin, end: end);
|
||||
|
||||
bool _dirty = true;
|
||||
|
||||
void _initialize() {
|
||||
assert(begin != null);
|
||||
assert(end != null);
|
||||
_centerArc = new MaterialPointArcTween(
|
||||
begin: begin.center,
|
||||
end: end.center,
|
||||
);
|
||||
_dirty = false;
|
||||
}
|
||||
|
||||
/// If [begin] and [end] are non-null, returns a tween that interpolates
|
||||
/// along a circular arc between [begin.center] and [end.center].
|
||||
MaterialPointArcTween get centerArc {
|
||||
if (begin == null || end == null)
|
||||
return null;
|
||||
if (_dirty)
|
||||
_initialize();
|
||||
return _centerArc;
|
||||
}
|
||||
MaterialPointArcTween _centerArc;
|
||||
|
||||
@override
|
||||
set begin(Rect value) {
|
||||
if (value != begin) {
|
||||
super.begin = value;
|
||||
_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
set end(Rect value) {
|
||||
if (value != end) {
|
||||
super.end = value;
|
||||
_dirty = true;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Rect lerp(double t) {
|
||||
if (_dirty)
|
||||
_initialize();
|
||||
if (t == 0.0)
|
||||
return begin;
|
||||
if (t == 1.0)
|
||||
return end;
|
||||
final Offset center = _centerArc.lerp(t);
|
||||
final double width = lerpDouble(begin.width, end.width, t);
|
||||
final double height = lerpDouble(begin.height, end.height, t);
|
||||
return new Rect.fromLTWH(center.dx - width / 2.0, center.dy - height / 2.0, width, height);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return '$runtimeType($begin \u2192 $end; centerArc=$centerArc)';
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,6 +80,7 @@ class Hero extends StatefulWidget {
|
|||
const Hero({
|
||||
Key key,
|
||||
@required this.tag,
|
||||
this.createRectTween,
|
||||
@required this.child,
|
||||
}) : assert(tag != null),
|
||||
assert(child != null),
|
||||
|
@ -90,6 +91,19 @@ class Hero extends StatefulWidget {
|
|||
/// a hero animation will be triggered.
|
||||
final Object tag;
|
||||
|
||||
/// Defines how the destination hero's bounds change as it flies from the starting
|
||||
/// route to the destination route.
|
||||
///
|
||||
/// A hero flight begins with the destination hero's [child] aligned with the
|
||||
/// starting hero's child. The [RectTween] returned by this callback is used
|
||||
/// to compute the hero's bounds as the flight animation's value goes from 0.0
|
||||
/// to 1.0.
|
||||
///
|
||||
/// If this property is null, the default, then the value of
|
||||
/// [HeroController.createRectTween] is used. The [HeroController] created by
|
||||
/// [MaterialApp] creates a [MaterialArcRectTween].
|
||||
final CreateRectTween createRectTween;
|
||||
|
||||
/// The widget subtree that will "fly" from one route to another during a
|
||||
/// [Navigator] push or pop transition.
|
||||
///
|
||||
|
@ -230,8 +244,9 @@ class _HeroFlight {
|
|||
bool _aborted = false;
|
||||
|
||||
RectTween _doCreateRectTween(Rect begin, Rect end) {
|
||||
if (manifest.createRectTween != null)
|
||||
return manifest.createRectTween(begin, end);
|
||||
final CreateRectTween createRectTween = manifest.toHero.widget.createRectTween ?? manifest.createRectTween;
|
||||
if (createRectTween != null)
|
||||
return createRectTween(begin, end);
|
||||
return new RectTween(begin: begin, end: end);
|
||||
}
|
||||
|
||||
|
|
|
@ -1005,4 +1005,119 @@ void main() {
|
|||
expect(find.text('456'), findsOneWidget);
|
||||
|
||||
});
|
||||
|
||||
testWidgets('Hero createRectTween', (WidgetTester tester) async {
|
||||
RectTween createRectTween(Rect begin, Rect end) {
|
||||
return new MaterialRectCenterArcTween(begin: begin, end: end);
|
||||
}
|
||||
|
||||
final Map<String, WidgetBuilder> createRectTweenHeroRoutes = <String, WidgetBuilder>{
|
||||
'/': (BuildContext context) => new Material(
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
new Hero(
|
||||
tag: 'a',
|
||||
createRectTween: createRectTween,
|
||||
child: new Container(height: 100.0, width: 100.0, key: firstKey),
|
||||
),
|
||||
new FlatButton(
|
||||
child: const Text('two'),
|
||||
onPressed: () { Navigator.pushNamed(context, '/two'); }
|
||||
),
|
||||
]
|
||||
)
|
||||
),
|
||||
'/two': (BuildContext context) => new Material(
|
||||
child: new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
new SizedBox(
|
||||
height: 200.0,
|
||||
child: new FlatButton(
|
||||
child: const Text('pop'),
|
||||
onPressed: () { Navigator.pop(context); }
|
||||
),
|
||||
),
|
||||
new Hero(
|
||||
tag: 'a',
|
||||
createRectTween: createRectTween,
|
||||
child: new Container(height: 200.0, width: 100.0, key: secondKey),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
};
|
||||
|
||||
await tester.pumpWidget(new MaterialApp(routes: createRectTweenHeroRoutes));
|
||||
expect(tester.getCenter(find.byKey(firstKey)), const Offset(50.0, 50.0));
|
||||
|
||||
final double epsilon = 0.001;
|
||||
final Duration duration = const Duration(milliseconds: 300);
|
||||
final Curve curve = Curves.fastOutSlowIn;
|
||||
final MaterialPointArcTween pushCenterTween = new MaterialPointArcTween(
|
||||
begin: const Offset(50.0, 50.0),
|
||||
end: const Offset(400.0, 300.0),
|
||||
);
|
||||
|
||||
await tester.tap(find.text('two'));
|
||||
await tester.pump(); // begin navigation
|
||||
|
||||
// Verify that the center of the secondKey Hero flies along the
|
||||
// pushCenterTween arc for the push /two flight.
|
||||
|
||||
await tester.pump();
|
||||
expect(tester.getCenter(find.byKey(secondKey)), const Offset(50.0, 50.0));
|
||||
|
||||
await tester.pump(duration * 0.25);
|
||||
Offset actualHeroCenter = tester.getCenter(find.byKey(secondKey));
|
||||
Offset predictedHeroCenter = pushCenterTween.lerp(curve.transform(0.25));
|
||||
expect((actualHeroCenter - predictedHeroCenter).distance, closeTo(0.0, epsilon));
|
||||
|
||||
await tester.pump(duration * 0.25);
|
||||
actualHeroCenter = tester.getCenter(find.byKey(secondKey));
|
||||
predictedHeroCenter = pushCenterTween.lerp(curve.transform(0.5));
|
||||
expect((actualHeroCenter - predictedHeroCenter).distance, closeTo(0.0, epsilon));
|
||||
|
||||
await tester.pump(duration * 0.25);
|
||||
actualHeroCenter = tester.getCenter(find.byKey(secondKey));
|
||||
predictedHeroCenter = pushCenterTween.lerp(curve.transform(0.75));
|
||||
expect((actualHeroCenter - predictedHeroCenter).distance, closeTo(0.0, epsilon));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.getCenter(find.byKey(secondKey)), const Offset(400.0, 300.0));
|
||||
|
||||
// Verify that the center of the firstKey Hero flies along the
|
||||
// pushCenterTween arc for the pop /two flight.
|
||||
|
||||
await tester.tap(find.text('pop'));
|
||||
await tester.pump(); // begin navigation
|
||||
|
||||
final MaterialPointArcTween popCenterTween = new MaterialPointArcTween(
|
||||
begin: const Offset(400.0, 300.0),
|
||||
end: const Offset(50.0, 50.0),
|
||||
);
|
||||
await tester.pump();
|
||||
expect(tester.getCenter(find.byKey(firstKey)), const Offset(400.0, 300.0));
|
||||
|
||||
await tester.pump(duration * 0.25);
|
||||
actualHeroCenter = tester.getCenter(find.byKey(firstKey));
|
||||
predictedHeroCenter = popCenterTween.lerp(curve.flipped.transform(0.25));
|
||||
expect((actualHeroCenter - predictedHeroCenter).distance, closeTo(0.0, epsilon));
|
||||
|
||||
await tester.pump(duration * 0.25);
|
||||
actualHeroCenter = tester.getCenter(find.byKey(firstKey));
|
||||
predictedHeroCenter = popCenterTween.lerp(curve.flipped.transform(0.5));
|
||||
expect((actualHeroCenter - predictedHeroCenter).distance, closeTo(0.0, epsilon));
|
||||
|
||||
await tester.pump(duration * 0.25);
|
||||
actualHeroCenter = tester.getCenter(find.byKey(firstKey));
|
||||
predictedHeroCenter = popCenterTween.lerp(curve.flipped.transform(0.75));
|
||||
expect((actualHeroCenter - predictedHeroCenter).distance, closeTo(0.0, epsilon));
|
||||
|
||||
await tester.pumpAndSettle();
|
||||
expect(tester.getCenter(find.byKey(firstKey)), const Offset(50.0, 50.0));
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue