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

diff --git a/packages/flutter/lib/src/widgets/heroes.dart b/packages/flutter/lib/src/widgets/heroes.dart
index e9c50c1..9966ab9 100644
--- a/packages/flutter/lib/src/widgets/heroes.dart
+++ b/packages/flutter/lib/src/widgets/heroes.dart
@@ -53,11 +53,15 @@
   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 @@
     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 @@
       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 @@
       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 @@
       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);
diff --git a/packages/flutter/test/widgets/heroes_test.dart b/packages/flutter/test/widgets/heroes_test.dart
index 3cf6679..83fa423 100644
--- a/packages/flutter/test/widgets/heroes_test.dart
+++ b/packages/flutter/test/widgets/heroes_test.dart
@@ -1172,6 +1172,128 @@
     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'));