Unverified Commit 2a2e0cf1 authored by RomanJos's avatar RomanJos Committed by GitHub

Add clipBehavior to InteractiveViewer (#73281)

More intuitive control over whether the child can overflow out of InteractiveViewer, deterring a lot of confusion we were seeing.
parent 2e3d3e65
...@@ -19,14 +19,16 @@ import 'ticker_provider.dart'; ...@@ -19,14 +19,16 @@ import 'ticker_provider.dart';
/// ///
/// The user can transform the child by dragging to pan or pinching to zoom. /// 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 /// By default, InteractiveViewer clips its child using [Clip.hardEdge].
/// screen, such as when a child is zoomed in and increases in size. However, it /// To prevent this behavior, consider setting [clipBehavior] to [Clip.none].
/// will not receive gestures outside of its original area. To prevent /// When [clipBehavior] is [Clip.none], InteractiveViewer may draw outside of
/// InteractiveViewer from drawing outside of its original size, wrap it in a /// its original area of the screen, such as when a child is zoomed in and
/// [ClipRect]. Or, to prevent dead areas where InteractiveViewer does not /// increases in size. However, it will not receive gestures outside of its original area.
/// receive gestures, be sure that the InteractiveViewer widget is the size of /// To prevent dead areas where InteractiveViewer does not receive gestures,
/// the area that should be interactive. See /// don't set [clipBehavior] or be sure that the InteractiveViewer widget is the
/// [flutter-go](https://github.com/justinmc/flutter-go) for an example of /// 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 /// robust positioning of an InteractiveViewer child that works for all screen
/// sizes and child sizes. /// sizes and child sizes.
/// ///
...@@ -68,6 +70,7 @@ class InteractiveViewer extends StatefulWidget { ...@@ -68,6 +70,7 @@ class InteractiveViewer extends StatefulWidget {
/// The [child] parameter must not be null. /// The [child] parameter must not be null.
InteractiveViewer({ InteractiveViewer({
Key? key, Key? key,
this.clipBehavior = Clip.hardEdge,
this.alignPanAxis = false, this.alignPanAxis = false,
this.boundaryMargin = EdgeInsets.zero, this.boundaryMargin = EdgeInsets.zero,
this.constrained = true, this.constrained = true,
...@@ -102,6 +105,13 @@ class InteractiveViewer extends StatefulWidget { ...@@ -102,6 +105,13 @@ class InteractiveViewer extends StatefulWidget {
&& boundaryMargin.left.isFinite)), && boundaryMargin.left.isFinite)),
super(key: key); 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 /// If true, panning is only allowed in the direction of the horizontal axis
/// or the vertical axis. /// or the vertical axis.
/// ///
...@@ -1061,15 +1071,20 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid ...@@ -1061,15 +1071,20 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
); );
if (!widget.constrained) { 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 = ClipRect(
child: OverflowBox( clipBehavior: widget.clipBehavior,
alignment: Alignment.topLeft, child: child,
minWidth: 0.0,
minHeight: 0.0,
maxWidth: double.infinity,
maxHeight: double.infinity,
child: child,
),
); );
} }
......
...@@ -985,6 +985,49 @@ void main() { ...@@ -985,6 +985,49 @@ void main() {
expect(scale, greaterThan(1.0)); expect(scale, greaterThan(1.0));
expect(transformationController.value.getMaxScaleOnAxis(), 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', () { group('getNearestPointOnLine', () {
......
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