diff --git a/packages/flutter/lib/src/widgets/interactive_viewer.dart b/packages/flutter/lib/src/widgets/interactive_viewer.dart index 62c020e1f0b..3b5549f32c6 100644 --- a/packages/flutter/lib/src/widgets/interactive_viewer.dart +++ b/packages/flutter/lib/src/widgets/interactive_viewer.dart @@ -19,14 +19,16 @@ import 'ticker_provider.dart'; /// /// The user can transform the child by dragging to pan or pinching to zoom. /// -/// By default, InteractiveViewer may draw outside of its original area of the -/// screen, such as when a child is zoomed in and increases in size. However, it -/// will not receive gestures outside of its original area. To prevent -/// InteractiveViewer from drawing outside of its original size, wrap it in a -/// [ClipRect]. Or, to prevent dead areas where InteractiveViewer does not -/// receive gestures, be sure that the InteractiveViewer widget is the size of -/// the area that should be interactive. See -/// [flutter-go](https://github.com/justinmc/flutter-go) for an example of +/// By default, InteractiveViewer clips its child using [Clip.hardEdge]. +/// To prevent this behavior, consider setting [clipBehavior] to [Clip.none]. +/// When [clipBehavior] is [Clip.none], InteractiveViewer may draw outside of +/// its original area of the screen, such as when a child is zoomed in and +/// increases in size. However, it will not receive gestures outside of its original area. +/// To prevent dead areas where InteractiveViewer does not receive gestures, +/// don't set [clipBehavior] or be sure that the InteractiveViewer widget is the +/// size of the area that should be interactive. +/// +/// See [flutter-go](https://github.com/justinmc/flutter-go) for an example of /// robust positioning of an InteractiveViewer child that works for all screen /// sizes and child sizes. /// @@ -68,6 +70,7 @@ class InteractiveViewer extends StatefulWidget { /// The [child] parameter must not be null. InteractiveViewer({ Key? key, + this.clipBehavior = Clip.hardEdge, this.alignPanAxis = false, this.boundaryMargin = EdgeInsets.zero, this.constrained = true, @@ -102,6 +105,13 @@ class InteractiveViewer extends StatefulWidget { && boundaryMargin.left.isFinite)), super(key: key); + /// If set to [Clip.none], the child may extend beyond the size of the InteractiveViewer, + /// but it will not receive gestures in these areas. + /// Be sure that the InteractiveViewer is the desired size when using [Clip.none]. + /// + /// Defaults to [Clip.hardEdge]. + final Clip clipBehavior; + /// If true, panning is only allowed in the direction of the horizontal axis /// or the vertical axis. /// @@ -1061,15 +1071,20 @@ class _InteractiveViewerState extends State with TickerProvid ); if (!widget.constrained) { + child = OverflowBox( + alignment: Alignment.topLeft, + minWidth: 0.0, + minHeight: 0.0, + maxWidth: double.infinity, + maxHeight: double.infinity, + child: child, + ); + } + + if (widget.clipBehavior != Clip.none) { child = ClipRect( - child: OverflowBox( - alignment: Alignment.topLeft, - minWidth: 0.0, - minHeight: 0.0, - maxWidth: double.infinity, - maxHeight: double.infinity, - child: child, - ), + clipBehavior: widget.clipBehavior, + child: child, ); } diff --git a/packages/flutter/test/widgets/interactive_viewer_test.dart b/packages/flutter/test/widgets/interactive_viewer_test.dart index 127f8bd46e9..c33cc5c0ad8 100644 --- a/packages/flutter/test/widgets/interactive_viewer_test.dart +++ b/packages/flutter/test/widgets/interactive_viewer_test.dart @@ -985,6 +985,49 @@ void main() { expect(scale, greaterThan(1.0)); expect(transformationController.value.getMaxScaleOnAxis(), greaterThan(1.0)); }); + + testWidgets('Check if ClipRect is present in the tree', (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: InteractiveViewer( + constrained: false, + clipBehavior: Clip.none, + minScale: 1.0, + maxScale: 1.0, + child: const SizedBox(width: 200.0, height: 200.0), + ), + ), + ), + ), + ); + + expect( + find.byType(ClipRect), + findsNothing, + ); + + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: Center( + child: InteractiveViewer( + constrained: false, + minScale: 1.0, + maxScale: 1.0, + child: const SizedBox(width: 200.0, height: 200.0), + ), + ), + ), + ), + ); + + expect( + find.byType(ClipRect), + findsOneWidget, + ); + }); }); group('getNearestPointOnLine', () {