Commit 9d16b84b authored by Sander Kersten's avatar Sander Kersten Committed by Michael Goderbauer

Fix calculation of hero rectTween when Navigator isn't fullscreen (#29677)

parent ae574981
......@@ -53,11 +53,15 @@ enum HeroFlightDirection {
pop,
}
// The bounding box for context in global coordinates.
Rect _globalBoundingBoxFor(BuildContext context) {
// The bounding box for context in ancestorContext coordinate system, or in the global
// coordinate system when null.
Rect _boundingBoxFor(BuildContext context, [BuildContext ancestorContext]) {
final RenderBox box = context.findRenderObject();
assert(box != null && box.hasSize);
return MatrixUtils.transformRect(box.getTransformTo(null), Offset.zero & box.size);
return MatrixUtils.transformRect(
box.getTransformTo(ancestorContext?.findRenderObject()),
Offset.zero & box.size,
);
}
/// A widget that marks its child as being a candidate for
......@@ -496,8 +500,8 @@ class _HeroFlight {
manifest.toHero.startFlight();
heroRectTween = _doCreateRectTween(
_globalBoundingBoxFor(manifest.fromHero.context),
_globalBoundingBoxFor(manifest.toHero.context),
_boundingBoxFor(manifest.fromHero.context, manifest.fromRoute.subtreeContext),
_boundingBoxFor(manifest.toHero.context, manifest.toRoute.subtreeContext),
);
overlayEntry = OverlayEntry(builder: _buildOverlay);
......@@ -540,7 +544,10 @@ class _HeroFlight {
if (manifest.fromHero != newManifest.toHero) {
manifest.fromHero.endFlight();
newManifest.toHero.startFlight();
heroRectTween = _doCreateRectTween(heroRectTween.end, _globalBoundingBoxFor(newManifest.toHero.context));
heroRectTween = _doCreateRectTween(
heroRectTween.end,
_boundingBoxFor(newManifest.toHero.context, newManifest.toRoute.subtreeContext),
);
} else {
// TODO(hansmuller): Use ReverseTween here per github.com/flutter/flutter/pull/12203.
heroRectTween = _doCreateRectTween(heroRectTween.end, heroRectTween.begin);
......@@ -552,7 +559,10 @@ class _HeroFlight {
assert(manifest.fromHero != newManifest.fromHero);
assert(manifest.toHero != newManifest.toHero);
heroRectTween = _doCreateRectTween(heroRectTween.evaluate(_proxyAnimation), _globalBoundingBoxFor(newManifest.toHero.context));
heroRectTween = _doCreateRectTween(
heroRectTween.evaluate(_proxyAnimation),
_boundingBoxFor(newManifest.toHero.context, newManifest.toRoute.subtreeContext),
);
shuttle = null;
if (newManifest.type == HeroFlightDirection.pop)
......@@ -706,7 +716,7 @@ class HeroController extends NavigatorObserver {
return;
}
final Rect navigatorRect = _globalBoundingBoxFor(navigator.context);
final Rect navigatorRect = _boundingBoxFor(navigator.context);
// At this point the toHeroes may have been built and laid out for the first time.
final Map<Object, _HeroState> fromHeroes = Hero._allHeroesFor(from.subtreeContext, isUserGestureTransition, navigator);
......
......@@ -1172,6 +1172,128 @@ void main() {
expect(tester.getCenter(find.byKey(firstKey)), const Offset(50.0, 50.0));
});
testWidgets('Hero createRectTween for Navigator that is not full screen', (WidgetTester tester) async {
// Regression test for https://github.com/flutter/flutter/issues/25272
RectTween createRectTween(Rect begin, Rect end) {
return RectTween(begin: begin, end: end);
}
final Map<String, WidgetBuilder> createRectTweenHeroRoutes = <String, WidgetBuilder>{
'/': (BuildContext context) => Material(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Hero(
tag: 'a',
createRectTween: createRectTween,
child: Container(height: 100.0, width: 100.0, key: firstKey),
),
FlatButton(
child: const Text('two'),
onPressed: () { Navigator.pushNamed(context, '/two'); },
),
],
),
),
'/two': (BuildContext context) => Material(
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
SizedBox(
height: 200.0,
child: FlatButton(
child: const Text('pop'),
onPressed: () { Navigator.pop(context); },
),
),
Hero(
tag: 'a',
createRectTween: createRectTween,
child: Container(height: 200.0, width: 100.0, key: secondKey),
),
],
),
),
};
const double leftPadding = 10.0;
// MaterialApp and its Navigator are offset from the left
await tester.pumpWidget(Padding(
padding: const EdgeInsets.only(left: leftPadding),
child: MaterialApp(routes: createRectTweenHeroRoutes),
));
expect(tester.getCenter(find.byKey(firstKey)), const Offset(leftPadding + 50.0, 50.0));
const double epsilon = 0.001;
const Duration duration = Duration(milliseconds: 300);
const Curve curve = Curves.fastOutSlowIn;
final RectTween pushRectTween = RectTween(
begin: Rect.fromLTWH(leftPadding, 0.0, 100.0, 100.0),
end: Rect.fromLTWH(350.0 + leftPadding / 2, 200.0, 100.0, 200.0),
);
await tester.tap(find.text('two'));
await tester.pump(); // begin navigation
// Verify that the rect of the secondKey Hero transforms as the
// pushRectTween rect for the push /two flight.
await tester.pump();
expect(tester.getCenter(find.byKey(secondKey)), const Offset(50.0 + leftPadding, 50.0));
await tester.pump(duration * 0.25);
Rect actualHeroRect = tester.getRect(find.byKey(secondKey));
Rect predictedHeroRect = pushRectTween.lerp(curve.transform(0.25));
expect(actualHeroRect, within<Rect>(distance: epsilon, from: predictedHeroRect));
await tester.pump(duration * 0.25);
actualHeroRect = tester.getRect(find.byKey(secondKey));
predictedHeroRect = pushRectTween.lerp(curve.transform(0.5));
expect(actualHeroRect, within<Rect>(distance: epsilon, from: predictedHeroRect));
await tester.pump(duration * 0.25);
actualHeroRect = tester.getRect(find.byKey(secondKey));
predictedHeroRect = pushRectTween.lerp(curve.transform(0.75));
expect(actualHeroRect, within<Rect>(distance: epsilon, from: predictedHeroRect));
await tester.pumpAndSettle();
expect(tester.getCenter(find.byKey(secondKey)), const Offset(400.0 + leftPadding / 2, 300.0));
// Verify that the rect of the firstKey Hero transforms as the
// pushRectTween rect for the pop /two flight.
await tester.tap(find.text('pop'));
await tester.pump(); // begin navigation
final RectTween popRectTween = RectTween(
begin: Rect.fromLTWH(350.0 + leftPadding / 2, 200.0, 100.0, 200.0),
end: Rect.fromLTWH(leftPadding, 0.0, 100.0, 100.0),
);
await tester.pump();
expect(tester.getCenter(find.byKey(firstKey)), const Offset(400.0 + leftPadding / 2, 300.0));
await tester.pump(duration * 0.25);
actualHeroRect = tester.getRect(find.byKey(firstKey));
predictedHeroRect = popRectTween.lerp(curve.flipped.transform(0.25));
expect(actualHeroRect, within<Rect>(distance: epsilon, from: predictedHeroRect));
await tester.pump(duration * 0.25);
actualHeroRect = tester.getRect(find.byKey(firstKey));
predictedHeroRect = popRectTween.lerp(curve.flipped.transform(0.5));
expect(actualHeroRect, within<Rect>(distance: epsilon, from: predictedHeroRect));
await tester.pump(duration * 0.25);
actualHeroRect = tester.getRect(find.byKey(firstKey));
predictedHeroRect = popRectTween.lerp(curve.flipped.transform(0.75));
expect(actualHeroRect, within<Rect>(distance: epsilon, from: predictedHeroRect));
await tester.pumpAndSettle();
expect(tester.getCenter(find.byKey(firstKey)), const Offset(50.0 + leftPadding, 50.0));
});
testWidgets('Pop interrupts push, reverses flight', (WidgetTester tester) async {
await tester.pumpWidget(MaterialApp(routes: routes));
await tester.tap(find.text('twoInset'));
......
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