diff --git a/packages/flutter/lib/src/widgets/interactive_viewer.dart b/packages/flutter/lib/src/widgets/interactive_viewer.dart index 15ea87b4d7f..e3c47c57a15 100644 --- a/packages/flutter/lib/src/widgets/interactive_viewer.dart +++ b/packages/flutter/lib/src/widgets/interactive_viewer.dart @@ -646,26 +646,21 @@ class _InteractiveViewerState extends State with TickerProvid // Don't allow a scale that results in an overall scale beyond min/max // scale. final double currentScale = _transformationController.value.getMaxScaleOnAxis(); - final double totalScale = currentScale * scale; + final double totalScale = math.max( + currentScale * scale, + // Ensure that the scale cannot make the child so big that it can't fit + // inside the boundaries (in either direction). + math.max( + _viewport.width / _boundaryRect.width, + _viewport.height / _boundaryRect.height, + ), + ); final double clampedTotalScale = totalScale.clamp( widget.minScale, widget.maxScale, ) as double; final double clampedScale = clampedTotalScale / currentScale; - final Matrix4 nextMatrix = matrix.clone()..scale(clampedScale); - - // Ensure that the scale cannot make the child so big that it can't fit - // inside the boundaries (in either direction). - final double minScale = math.max( - _viewport.width / _boundaryRect.width, - _viewport.height / _boundaryRect.height, - ); - if (clampedTotalScale < minScale) { - final double minCurrentScale = minScale / currentScale; - return matrix.clone()..scale(minCurrentScale); - } - - return nextMatrix; + return matrix.clone()..scale(clampedScale); } // Return a new matrix representing the given matrix after applying the given diff --git a/packages/flutter/test/widgets/interactive_viewer_test.dart b/packages/flutter/test/widgets/interactive_viewer_test.dart index 189d7bd9484..de3336a69d7 100644 --- a/packages/flutter/test/widgets/interactive_viewer_test.dart +++ b/packages/flutter/test/widgets/interactive_viewer_test.dart @@ -729,6 +729,47 @@ void main() { await tester.pumpAndSettle(); expect(transformationController.value.getMaxScaleOnAxis(), greaterThan(1.0)); }); + + // Regression test for https://github.com/flutter/flutter/issues/65304 + testWidgets('can view beyond boundary when necessary for a small child', (WidgetTester tester) async { + final TransformationController transformationController = TransformationController(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: InteractiveViewer( + constrained: false, + minScale: 1.0, + maxScale: 1.0, + transformationController: transformationController, + child: const SizedBox(width: 200.0, height: 200.0), + ), + ), + ), + ), + ); + + expect(transformationController.value, equals(Matrix4.identity())); + + // Pinch to zoom does nothing because minScale and maxScale are 1.0. + final Offset center = tester.getCenter(find.byType(SizedBox)); + final Offset scaleStart1 = Offset(center.dx - 10.0, center.dy - 10.0); + final Offset scaleStart2 = Offset(center.dx + 10.0, center.dy + 10.0); + final Offset scaleEnd1 = Offset(center.dx - 20.0, center.dy - 20.0); + final Offset scaleEnd2 = Offset(center.dx + 20.0, center.dy + 20.0); + final TestGesture gesture = await tester.createGesture(); + final TestGesture gesture2 = await tester.createGesture(); + await gesture.down(scaleStart1); + await gesture2.down(scaleStart2); + await tester.pump(); + await gesture.moveTo(scaleEnd1); + await gesture2.moveTo(scaleEnd2); + await tester.pump(); + await gesture.up(); + await gesture2.up(); + await tester.pumpAndSettle(); + expect(transformationController.value, equals(Matrix4.identity())); + }); }); group('getNearestPointOnLine', () {