Unverified Commit 4e3b0115 authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

InteractiveViewer Scale Jump (#71497)

parent df748f1a
......@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:math' as math;
import 'package:vector_math/vector_math_64.dart';
......@@ -226,10 +225,42 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
ScaleGestureRecognizer({
Object? debugOwner,
PointerDeviceKind? kind,
}) : super(debugOwner: debugOwner, kind: kind);
this.dragStartBehavior = DragStartBehavior.down,
}) : assert(dragStartBehavior != null),
super(debugOwner: debugOwner, kind: kind);
/// Determines what point is used as the starting point in all calculations
/// involving this gesture.
///
/// When set to [DragStartBehavior.down], the scale is calculated starting
/// from the position where the pointer first contacted the screen.
///
/// When set to [DragStartBehavior.start], the scale is calculated starting
/// from the position where the scale gesture began. The scale gesture may
/// begin after the time that the pointer first contacted the screen if there
/// are multiple listeners competing for the gesture. In that case, the
/// gesture arena waits to determine whether or not the gesture is a scale
/// gesture before giving the gesture to this GestureRecognizer. This happens
/// in the case of nested GestureDetectors, for example.
///
/// Defaults to [DragStartBehavior.down].
///
/// See also:
///
/// * [https://flutter.dev/docs/development/ui/advanced/gestures#gesture-disambiguation],
/// which provides more information about the gesture arena.
DragStartBehavior dragStartBehavior;
/// The pointers in contact with the screen have established a focal point and
/// initial scale of 1.0.
///
/// This won't be called until the gesture arena has determined that this
/// GestureRecognizer has won the gesture.
///
/// See also:
///
/// * [https://flutter.dev/docs/development/ui/advanced/gestures#gesture-disambiguation],
/// which provides more information about the gesture arena.
GestureScaleStartCallback? onStart;
/// The pointers in contact with the screen have indicated a new focal point
......@@ -461,6 +492,13 @@ class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
if (_state == _ScaleState.possible) {
_state = _ScaleState.started;
_dispatchOnStartCallbackIfNeeded();
if (dragStartBehavior == DragStartBehavior.start) {
_initialFocalPoint = _currentFocalPoint;
_initialSpan = _currentSpan;
_initialLine = _currentLine;
_initialHorizontalSpan = _currentHorizontalSpan;
_initialVerticalSpan = _currentVerticalSpan;
}
}
}
......
......@@ -897,7 +897,8 @@ class GestureDetector extends StatelessWidget {
instance
..onStart = onScaleStart
..onUpdate = onScaleUpdate
..onEnd = onScaleEnd;
..onEnd = onScaleEnd
..dragStartBehavior = dragStartBehavior;
},
);
}
......
......@@ -1078,6 +1078,7 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
onPointerSignal: _receivedPointerSignal,
child: GestureDetector(
behavior: HitTestBehavior.opaque, // Necessary when panning off screen.
dragStartBehavior: DragStartBehavior.start,
onScaleEnd: _onScaleEnd,
onScaleStart: _onScaleStart,
onScaleUpdate: _onScaleUpdate,
......
......@@ -907,6 +907,84 @@ void main() {
await tester.pumpAndSettle();
expect(transformationController.value, equals(Matrix4.identity()));
});
testWidgets('scale does not jump when wrapped in GestureDetector', (WidgetTester tester) async {
final TransformationController transformationController = TransformationController();
double? initialScale;
double? scale;
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: Center(
child: GestureDetector(
onTapUp: (TapUpDetails details) {},
child: InteractiveViewer(
onInteractionUpdate: (ScaleUpdateDetails details) {
initialScale ??= details.scale;
scale = details.scale;
},
transformationController: transformationController,
child: Container(width: 200.0, height: 200.0),
),
),
),
),
),
);
expect(transformationController.value, equals(Matrix4.identity()));
expect(initialScale, null);
expect(scale, null);
// Pinch to zoom isn't immediately detected for a small amount of
// movement due to the GestureDetector.
final Offset childOffset = tester.getTopLeft(find.byType(Container));
final Offset childInterior = Offset(
childOffset.dx + 20.0,
childOffset.dy + 20.0,
);
final Offset scaleStart1 = childInterior;
final Offset scaleStart2 = Offset(childInterior.dx + 10.0, childInterior.dy);
Offset scaleEnd1 = Offset(childInterior.dx - 10.0, childInterior.dy);
Offset scaleEnd2 = Offset(childInterior.dx + 20.0, childInterior.dy);
TestGesture gesture = await tester.createGesture();
TestGesture gesture2 = await tester.createGesture();
addTearDown(gesture.removePointer);
addTearDown(gesture2.removePointer);
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()));
expect(initialScale, null);
expect(scale, null);
// Pinch to zoom for a larger amount is detected. It starts smoothly at
// 1.0 despite the fact that the gesture has already moved a bit.
scaleEnd1 = Offset(childInterior.dx - 38.0, childInterior.dy);
scaleEnd2 = Offset(childInterior.dx + 48.0, childInterior.dy);
gesture = await tester.createGesture();
gesture2 = await tester.createGesture();
addTearDown(gesture.removePointer);
addTearDown(gesture2.removePointer);
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(initialScale, 1.0);
expect(scale, greaterThan(1.0));
expect(transformationController.value.getMaxScaleOnAxis(), greaterThan(1.0));
});
});
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