mirror of
https://github.com/flutter/flutter
synced 2024-10-13 03:32:55 +00:00
InteractiveViewer should call onInteractionUpdate even when gesture is disabled (#78990)
This commit is contained in:
parent
97a75b9c46
commit
4982a7f185
|
@ -253,11 +253,12 @@ class InteractiveViewer extends StatefulWidget {
|
|||
/// Called when the user ends a pan or scale gesture on the widget.
|
||||
///
|
||||
/// At the time this is called, the [TransformationController] will have
|
||||
/// already been updated to reflect the change caused by the interaction.
|
||||
/// already been updated to reflect the change caused by the interaction,
|
||||
/// though a pan may cause an inertia animation after this is called as well.
|
||||
///
|
||||
/// {@template flutter.widgets.InteractiveViewer.onInteractionEnd}
|
||||
/// Will be called even if the interaction is disabled with
|
||||
/// [panEnabled] or [scaleEnabled].
|
||||
/// Will be called even if the interaction is disabled with [panEnabled] or
|
||||
/// [scaleEnabled] for both touch gestures and mouse interactions.
|
||||
///
|
||||
/// A [GestureDetector] wrapping the InteractiveViewer will not respond to
|
||||
/// [GestureDetector.onScaleStart], [GestureDetector.onScaleUpdate], and
|
||||
|
@ -294,7 +295,8 @@ class InteractiveViewer extends StatefulWidget {
|
|||
/// Called when the user updates a pan or scale gesture on the widget.
|
||||
///
|
||||
/// At the time this is called, the [TransformationController] will have
|
||||
/// already been updated to reflect the change caused by the interaction.
|
||||
/// already been updated to reflect the change caused by the interaction, if
|
||||
/// the interation caused the matrix to change.
|
||||
///
|
||||
/// {@macro flutter.widgets.InteractiveViewer.onInteractionEnd}
|
||||
///
|
||||
|
@ -796,6 +798,7 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
|||
_gestureType ??= _getGestureType(details);
|
||||
}
|
||||
if (!_gestureIsSupported(_gestureType)) {
|
||||
widget.onInteractionUpdate?.call(details);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -839,6 +842,7 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
|||
|
||||
case _GestureType.rotate:
|
||||
if (details.rotation == 0.0) {
|
||||
widget.onInteractionUpdate?.call(details);
|
||||
return;
|
||||
}
|
||||
final double desiredRotation = _rotationStart! + details.rotation;
|
||||
|
@ -856,6 +860,7 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
|||
// In an effort to keep the behavior similar whether or not scaleEnabled
|
||||
// is true, these gestures are thrown away.
|
||||
if (details.scale != 1.0) {
|
||||
widget.onInteractionUpdate?.call(details);
|
||||
return;
|
||||
}
|
||||
_panAxis ??= _getPanAxis(_referenceFocalPoint!, focalPointScene);
|
||||
|
@ -871,12 +876,7 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
|||
);
|
||||
break;
|
||||
}
|
||||
widget.onInteractionUpdate?.call(ScaleUpdateDetails(
|
||||
focalPoint: details.focalPoint,
|
||||
localFocalPoint: details.localFocalPoint,
|
||||
scale: details.scale,
|
||||
rotation: details.rotation,
|
||||
));
|
||||
widget.onInteractionUpdate?.call(details);
|
||||
}
|
||||
|
||||
// Handle the end of a gesture of _GestureType. All of pan, scale, and rotate
|
||||
|
@ -932,25 +932,36 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
|||
// Handle mousewheel scroll events.
|
||||
void _receivedPointerSignal(PointerSignalEvent event) {
|
||||
if (event is PointerScrollEvent) {
|
||||
// Ignore left and right scroll.
|
||||
if (event.scrollDelta.dy == 0.0) {
|
||||
return;
|
||||
}
|
||||
widget.onInteractionStart?.call(
|
||||
ScaleStartDetails(
|
||||
focalPoint: event.position,
|
||||
localFocalPoint: event.localPosition,
|
||||
),
|
||||
);
|
||||
|
||||
// In the Flutter engine, the mousewheel scrollDelta is hardcoded to 20
|
||||
// per scroll, while a trackpad scroll can be any amount. The calculation
|
||||
// for scaleChange here was arbitrarily chosen to feel natural for both
|
||||
// trackpads and mousewheels on all platforms.
|
||||
final double scaleChange = math.exp(-event.scrollDelta.dy / 200);
|
||||
|
||||
if (!_gestureIsSupported(_GestureType.scale)) {
|
||||
widget.onInteractionUpdate?.call(ScaleUpdateDetails(
|
||||
focalPoint: event.position,
|
||||
localFocalPoint: event.localPosition,
|
||||
rotation: 0.0,
|
||||
scale: scaleChange,
|
||||
horizontalScale: 1.0,
|
||||
verticalScale: 1.0,
|
||||
));
|
||||
widget.onInteractionEnd?.call(ScaleEndDetails());
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore left and right scroll.
|
||||
if (event.scrollDelta.dy == 0.0) {
|
||||
return;
|
||||
}
|
||||
|
||||
// In the Flutter engine, the mousewheel scrollDelta is hardcoded to 20 per scroll, while a trackpad scroll can be any amount.
|
||||
// The calculation for scaleChange here was arbitrarily chosen to feel natural for both trackpads and mousewheels on all platforms.
|
||||
final double scaleChange = math.exp(-event.scrollDelta.dy / 200);
|
||||
final Offset focalPointScene = _transformationController!.toScene(
|
||||
event.localPosition,
|
||||
);
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:vector_math/vector_math_64.dart' show Quad, Vector3, Matrix4;
|
||||
|
@ -717,15 +718,15 @@ void main() {
|
|||
body: Center(
|
||||
child: InteractiveViewer(
|
||||
transformationController: transformationController,
|
||||
onInteractionStart: (ScaleStartDetails details){
|
||||
onInteractionStart: (ScaleStartDetails details) {
|
||||
calledStart = true;
|
||||
},
|
||||
onInteractionUpdate: (ScaleUpdateDetails details){
|
||||
onInteractionUpdate: (ScaleUpdateDetails details) {
|
||||
scaleChange = details.scale;
|
||||
focalPoint = details.focalPoint;
|
||||
localFocalPoint = details.localFocalPoint;
|
||||
},
|
||||
onInteractionEnd: (ScaleEndDetails details){
|
||||
onInteractionEnd: (ScaleEndDetails details) {
|
||||
currentVelocity = details.velocity;
|
||||
},
|
||||
child: const SizedBox(width: 200.0, height: 200.0),
|
||||
|
@ -758,6 +759,140 @@ void main() {
|
|||
expect(scenePoint.dy, greaterThan(0.0));
|
||||
});
|
||||
|
||||
testWidgets('onInteraction is called even when disabled (touch)', (WidgetTester tester) async {
|
||||
final TransformationController transformationController = TransformationController();
|
||||
bool calledStart = false;
|
||||
bool calledUpdate = false;
|
||||
bool calledEnd = false;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: InteractiveViewer(
|
||||
transformationController: transformationController,
|
||||
scaleEnabled: false,
|
||||
onInteractionStart: (ScaleStartDetails details) {
|
||||
calledStart = true;
|
||||
},
|
||||
onInteractionUpdate: (ScaleUpdateDetails details) {
|
||||
calledUpdate = true;
|
||||
},
|
||||
onInteractionEnd: (ScaleEndDetails details) {
|
||||
calledEnd = true;
|
||||
},
|
||||
child: const SizedBox(width: 200.0, height: 200.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final Offset childOffset = tester.getTopLeft(find.byType(SizedBox));
|
||||
final Offset childInterior = Offset(
|
||||
childOffset.dx + 20.0,
|
||||
childOffset.dy + 20.0,
|
||||
);
|
||||
TestGesture gesture = await tester.startGesture(childOffset);
|
||||
|
||||
// Attempting to pan doesn't work because it's disabled, but the
|
||||
// interaction methods are still called.
|
||||
addTearDown(gesture.removePointer);
|
||||
await tester.pump();
|
||||
await gesture.moveTo(childInterior);
|
||||
await tester.pump();
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
expect(transformationController.value, equals(Matrix4.identity()));
|
||||
expect(calledStart, isTrue);
|
||||
expect(calledUpdate, isTrue);
|
||||
expect(calledEnd, isTrue);
|
||||
|
||||
// Attempting to pinch to zoom doesn't work because it's disabled, but the
|
||||
// interaction methods are still called.
|
||||
calledStart = false;
|
||||
calledUpdate = false;
|
||||
calledEnd = false;
|
||||
final Offset scaleStart1 = childInterior;
|
||||
final Offset scaleStart2 = Offset(childInterior.dx + 10.0, childInterior.dy);
|
||||
final Offset scaleEnd1 = Offset(childInterior.dx - 10.0, childInterior.dy);
|
||||
final Offset scaleEnd2 = Offset(childInterior.dx + 20.0, childInterior.dy);
|
||||
gesture = await tester.startGesture(scaleStart1);
|
||||
final TestGesture gesture2 = await tester.startGesture(scaleStart2);
|
||||
addTearDown(gesture2.removePointer);
|
||||
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(calledStart, isTrue);
|
||||
expect(calledUpdate, isTrue);
|
||||
expect(calledEnd, isTrue);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android, TargetPlatform.iOS }));
|
||||
|
||||
testWidgets('onInteraction is called even when disabled (mouse)', (WidgetTester tester) async {
|
||||
final TransformationController transformationController = TransformationController();
|
||||
bool calledStart = false;
|
||||
bool calledUpdate = false;
|
||||
bool calledEnd = false;
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: InteractiveViewer(
|
||||
transformationController: transformationController,
|
||||
scaleEnabled: false,
|
||||
onInteractionStart: (ScaleStartDetails details) {
|
||||
calledStart = true;
|
||||
},
|
||||
onInteractionUpdate: (ScaleUpdateDetails details) {
|
||||
calledUpdate = true;
|
||||
},
|
||||
onInteractionEnd: (ScaleEndDetails details) {
|
||||
calledEnd = true;
|
||||
},
|
||||
child: const SizedBox(width: 200.0, height: 200.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
final Offset childOffset = tester.getTopLeft(find.byType(SizedBox));
|
||||
final Offset childInterior = Offset(
|
||||
childOffset.dx + 20.0,
|
||||
childOffset.dy + 20.0,
|
||||
);
|
||||
final TestGesture gesture = await tester.startGesture(childOffset, kind: PointerDeviceKind.mouse);
|
||||
|
||||
// Attempting to pan doesn't work because it's disabled, but the
|
||||
// interaction methods are still called.
|
||||
addTearDown(gesture.removePointer);
|
||||
await tester.pump();
|
||||
await gesture.moveTo(childInterior);
|
||||
await tester.pump();
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
expect(transformationController.value, equals(Matrix4.identity()));
|
||||
expect(calledStart, isTrue);
|
||||
expect(calledUpdate, isTrue);
|
||||
expect(calledEnd, isTrue);
|
||||
|
||||
// Attempting to scroll with a mouse to zoom doesn't work because it's
|
||||
// disabled, but the interaction methods are still called.
|
||||
calledStart = false;
|
||||
calledUpdate = false;
|
||||
calledEnd = false;
|
||||
await scrollAt(childInterior, tester, const Offset(0.0, -20.0));
|
||||
await tester.pumpAndSettle();
|
||||
expect(transformationController.value, equals(Matrix4.identity()));
|
||||
expect(calledStart, isTrue);
|
||||
expect(calledUpdate, isTrue);
|
||||
expect(calledEnd, isTrue);
|
||||
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.macOS, TargetPlatform.linux, TargetPlatform.windows }));
|
||||
|
||||
testWidgets('viewport changes size', (WidgetTester tester) async {
|
||||
final TransformationController transformationController = TransformationController();
|
||||
await tester.pumpWidget(
|
||||
|
|
Loading…
Reference in a new issue