InteractiveViewer onInteractionUpdate focalPoint (#66065)
This commit is contained in:
parent
a19f5baccc
commit
2c163e6e95
@ -220,6 +220,9 @@ class InteractiveViewer extends StatefulWidget {
|
|||||||
|
|
||||||
/// Called when the user ends a pan or scale gesture on the widget.
|
/// 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.
|
||||||
|
///
|
||||||
/// {@template flutter.widgets.interactiveViewer.onInteraction}
|
/// {@template flutter.widgets.interactiveViewer.onInteraction}
|
||||||
/// Will be called even if the interaction is disabled with
|
/// Will be called even if the interaction is disabled with
|
||||||
/// [panEnabled] or [scaleEnabled].
|
/// [panEnabled] or [scaleEnabled].
|
||||||
@ -229,10 +232,6 @@ class InteractiveViewer extends StatefulWidget {
|
|||||||
/// [GestureDetector.onScaleEnd]. Use [onInteractionStart],
|
/// [GestureDetector.onScaleEnd]. Use [onInteractionStart],
|
||||||
/// [onInteractionUpdate], and [onInteractionEnd] to respond to those
|
/// [onInteractionUpdate], and [onInteractionEnd] to respond to those
|
||||||
/// gestures.
|
/// gestures.
|
||||||
///
|
|
||||||
/// The coordinates returned in the details are viewport coordinates relative
|
|
||||||
/// to the parent. See [TransformationController.toScene] for how to
|
|
||||||
/// convert the coordinates to scene coordinates relative to the child.
|
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
@ -243,8 +242,17 @@ class InteractiveViewer extends StatefulWidget {
|
|||||||
|
|
||||||
/// Called when the user begins a pan or scale gesture on the widget.
|
/// Called when the user begins a pan or scale gesture on the widget.
|
||||||
///
|
///
|
||||||
|
/// At the time this is called, the [TransformationController] will not have
|
||||||
|
/// changed due to this interaction.
|
||||||
|
///
|
||||||
/// {@macro flutter.widgets.interactiveViewer.onInteraction}
|
/// {@macro flutter.widgets.interactiveViewer.onInteraction}
|
||||||
///
|
///
|
||||||
|
/// The coordinates provided in the details' `focalPoint` and
|
||||||
|
/// `localFocalPoint` are normal Flutter event coordinates, not
|
||||||
|
/// InteractiveViewer scene coordinates. See
|
||||||
|
/// [TransformationController.toScene] for how to convert these coordinates to
|
||||||
|
/// scene coordinates relative to the child.
|
||||||
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [onInteractionUpdate], which handles an update to the same interaction.
|
/// * [onInteractionUpdate], which handles an update to the same interaction.
|
||||||
@ -253,8 +261,17 @@ class InteractiveViewer extends StatefulWidget {
|
|||||||
|
|
||||||
/// Called when the user updates a pan or scale gesture on the widget.
|
/// 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.
|
||||||
|
///
|
||||||
/// {@macro flutter.widgets.interactiveViewer.onInteraction}
|
/// {@macro flutter.widgets.interactiveViewer.onInteraction}
|
||||||
///
|
///
|
||||||
|
/// The coordinates provided in the details' `focalPoint` and
|
||||||
|
/// `localFocalPoint` are normal Flutter event coordinates, not
|
||||||
|
/// InteractiveViewer scene coordinates. See
|
||||||
|
/// [TransformationController.toScene] for how to convert these coordinates to
|
||||||
|
/// scene coordinates relative to the child.
|
||||||
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
///
|
///
|
||||||
/// * [onInteractionStart], which handles the start of the same interaction.
|
/// * [onInteractionStart], which handles the start of the same interaction.
|
||||||
@ -711,9 +728,7 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
|||||||
// Handle the start of a gesture. All of pan, scale, and rotate are handled
|
// Handle the start of a gesture. All of pan, scale, and rotate are handled
|
||||||
// with GestureDetector's scale gesture.
|
// with GestureDetector's scale gesture.
|
||||||
void _onScaleStart(ScaleStartDetails details) {
|
void _onScaleStart(ScaleStartDetails details) {
|
||||||
if (widget.onInteractionStart != null) {
|
widget.onInteractionStart?.call(details);
|
||||||
widget.onInteractionStart!(details);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_controller.isAnimating) {
|
if (_controller.isAnimating) {
|
||||||
_controller.stop();
|
_controller.stop();
|
||||||
@ -735,16 +750,6 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
|||||||
// handled with GestureDetector's scale gesture.
|
// handled with GestureDetector's scale gesture.
|
||||||
void _onScaleUpdate(ScaleUpdateDetails details) {
|
void _onScaleUpdate(ScaleUpdateDetails details) {
|
||||||
final double scale = _transformationController!.value.getMaxScaleOnAxis();
|
final double scale = _transformationController!.value.getMaxScaleOnAxis();
|
||||||
if (widget.onInteractionUpdate != null) {
|
|
||||||
widget.onInteractionUpdate!(ScaleUpdateDetails(
|
|
||||||
focalPoint: _transformationController!.toScene(
|
|
||||||
details.localFocalPoint,
|
|
||||||
),
|
|
||||||
scale: details.scale,
|
|
||||||
rotation: details.rotation,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
final Offset focalPointScene = _transformationController!.toScene(
|
final Offset focalPointScene = _transformationController!.toScene(
|
||||||
details.localFocalPoint,
|
details.localFocalPoint,
|
||||||
);
|
);
|
||||||
@ -798,7 +803,7 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
|||||||
if (_round(_referenceFocalPoint!) != _round(focalPointSceneCheck)) {
|
if (_round(_referenceFocalPoint!) != _round(focalPointSceneCheck)) {
|
||||||
_referenceFocalPoint = focalPointSceneCheck;
|
_referenceFocalPoint = focalPointSceneCheck;
|
||||||
}
|
}
|
||||||
return;
|
break;
|
||||||
|
|
||||||
case _GestureType.rotate:
|
case _GestureType.rotate:
|
||||||
if (details.rotation == 0.0) {
|
if (details.rotation == 0.0) {
|
||||||
@ -811,7 +816,7 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
|||||||
details.localFocalPoint,
|
details.localFocalPoint,
|
||||||
);
|
);
|
||||||
_currentRotation = desiredRotation;
|
_currentRotation = desiredRotation;
|
||||||
return;
|
break;
|
||||||
|
|
||||||
case _GestureType.pan:
|
case _GestureType.pan:
|
||||||
assert(_referenceFocalPoint != null);
|
assert(_referenceFocalPoint != null);
|
||||||
@ -832,16 +837,20 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
|||||||
_referenceFocalPoint = _transformationController!.toScene(
|
_referenceFocalPoint = _transformationController!.toScene(
|
||||||
details.localFocalPoint,
|
details.localFocalPoint,
|
||||||
);
|
);
|
||||||
return;
|
break;
|
||||||
}
|
}
|
||||||
|
widget.onInteractionUpdate?.call(ScaleUpdateDetails(
|
||||||
|
focalPoint: details.focalPoint,
|
||||||
|
localFocalPoint: details.localFocalPoint,
|
||||||
|
scale: details.scale,
|
||||||
|
rotation: details.rotation,
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle the end of a gesture of _GestureType. All of pan, scale, and rotate
|
// Handle the end of a gesture of _GestureType. All of pan, scale, and rotate
|
||||||
// are handled with GestureDetector's scale gesture.
|
// are handled with GestureDetector's scale gesture.
|
||||||
void _onScaleEnd(ScaleEndDetails details) {
|
void _onScaleEnd(ScaleEndDetails details) {
|
||||||
if (widget.onInteractionEnd != null) {
|
widget.onInteractionEnd?.call(details);
|
||||||
widget.onInteractionEnd!(details);
|
|
||||||
}
|
|
||||||
_scaleStart = null;
|
_scaleStart = null;
|
||||||
_rotationStart = null;
|
_rotationStart = null;
|
||||||
_referenceFocalPoint = null;
|
_referenceFocalPoint = null;
|
||||||
@ -890,10 +899,17 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
|||||||
|
|
||||||
// Handle mousewheel scroll events.
|
// Handle mousewheel scroll events.
|
||||||
void _receivedPointerSignal(PointerSignalEvent event) {
|
void _receivedPointerSignal(PointerSignalEvent event) {
|
||||||
|
if (event is PointerScrollEvent) {
|
||||||
|
widget.onInteractionStart?.call(
|
||||||
|
ScaleStartDetails(
|
||||||
|
focalPoint: event.position,
|
||||||
|
localFocalPoint: event.localPosition,
|
||||||
|
),
|
||||||
|
);
|
||||||
if (!_gestureIsSupported(_GestureType.scale)) {
|
if (!_gestureIsSupported(_GestureType.scale)) {
|
||||||
|
widget.onInteractionEnd?.call(ScaleEndDetails());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (event is PointerScrollEvent) {
|
|
||||||
final RenderBox childRenderBox = _childKey.currentContext!.findRenderObject() as RenderBox;
|
final RenderBox childRenderBox = _childKey.currentContext!.findRenderObject() as RenderBox;
|
||||||
final Size childSize = childRenderBox.size;
|
final Size childSize = childRenderBox.size;
|
||||||
final double scaleChange = 1.0 - event.scrollDelta.dy / childSize.height;
|
final double scaleChange = 1.0 - event.scrollDelta.dy / childSize.height;
|
||||||
@ -903,6 +919,7 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
|||||||
final Offset focalPointScene = _transformationController!.toScene(
|
final Offset focalPointScene = _transformationController!.toScene(
|
||||||
event.localPosition,
|
event.localPosition,
|
||||||
);
|
);
|
||||||
|
|
||||||
_transformationController!.value = _matrixScale(
|
_transformationController!.value = _matrixScale(
|
||||||
_transformationController!.value,
|
_transformationController!.value,
|
||||||
scaleChange,
|
scaleChange,
|
||||||
@ -917,22 +934,16 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
|||||||
_transformationController!.value,
|
_transformationController!.value,
|
||||||
focalPointSceneScaled - focalPointScene,
|
focalPointSceneScaled - focalPointScene,
|
||||||
);
|
);
|
||||||
if (widget.onInteractionStart != null) {
|
|
||||||
widget.onInteractionStart!(
|
widget.onInteractionUpdate?.call(ScaleUpdateDetails(
|
||||||
ScaleStartDetails(focalPoint: focalPointSceneScaled)
|
focalPoint: event.position,
|
||||||
);
|
localFocalPoint: event.localPosition,
|
||||||
}
|
|
||||||
if (widget.onInteractionUpdate != null) {
|
|
||||||
widget.onInteractionUpdate!(ScaleUpdateDetails(
|
|
||||||
rotation: 0.0,
|
rotation: 0.0,
|
||||||
scale: scaleChange,
|
scale: scaleChange,
|
||||||
horizontalScale: 1.0,
|
horizontalScale: 1.0,
|
||||||
verticalScale: 1.0,
|
verticalScale: 1.0,
|
||||||
));
|
));
|
||||||
}
|
widget.onInteractionEnd?.call(ScaleEndDetails());
|
||||||
if (widget.onInteractionEnd != null) {
|
|
||||||
widget.onInteractionEnd!(ScaleEndDetails());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -625,6 +625,8 @@ void main() {
|
|||||||
|
|
||||||
testWidgets('Scale with mouse returns onInteraction properties', (WidgetTester tester) async{
|
testWidgets('Scale with mouse returns onInteraction properties', (WidgetTester tester) async{
|
||||||
final TransformationController transformationController = TransformationController();
|
final TransformationController transformationController = TransformationController();
|
||||||
|
Offset focalPoint;
|
||||||
|
Offset localFocalPoint;
|
||||||
double scaleChange;
|
double scaleChange;
|
||||||
Velocity currentVelocity;
|
Velocity currentVelocity;
|
||||||
bool calledStart;
|
bool calledStart;
|
||||||
@ -639,6 +641,8 @@ void main() {
|
|||||||
},
|
},
|
||||||
onInteractionUpdate: (ScaleUpdateDetails details){
|
onInteractionUpdate: (ScaleUpdateDetails details){
|
||||||
scaleChange = details.scale;
|
scaleChange = details.scale;
|
||||||
|
focalPoint = details.focalPoint;
|
||||||
|
localFocalPoint = details.localFocalPoint;
|
||||||
},
|
},
|
||||||
onInteractionEnd: (ScaleEndDetails details){
|
onInteractionEnd: (ScaleEndDetails details){
|
||||||
currentVelocity = details.velocity;
|
currentVelocity = details.velocity;
|
||||||
@ -657,10 +661,72 @@ void main() {
|
|||||||
final double afterScaling = transformationController.value.getMaxScaleOnAxis();
|
final double afterScaling = transformationController.value.getMaxScaleOnAxis();
|
||||||
|
|
||||||
expect(scaleChange, greaterThan(1.0));
|
expect(scaleChange, greaterThan(1.0));
|
||||||
expect(afterScaling, isNot(equals(null)));
|
expect(afterScaling, scaleChange);
|
||||||
expect(afterScaling, isNot(equals(1.0)));
|
|
||||||
expect(currentVelocity, equals(noMovement));
|
expect(currentVelocity, equals(noMovement));
|
||||||
expect(calledStart, equals(true));
|
expect(calledStart, equals(true));
|
||||||
|
// Focal points are given in coordinates outside of InteractiveViewer,
|
||||||
|
// with local being in relation to the viewport.
|
||||||
|
expect(focalPoint, center);
|
||||||
|
expect(localFocalPoint, const Offset(100, 100));
|
||||||
|
|
||||||
|
// The scene point is the same as localFocalPoint because the center of
|
||||||
|
// the scene is at the center of the viewport.
|
||||||
|
final Offset scenePoint = transformationController.toScene(localFocalPoint);
|
||||||
|
expect(scenePoint, const Offset(100, 100));
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('onInteraction can be used to get scene point', (WidgetTester tester) async{
|
||||||
|
final TransformationController transformationController = TransformationController();
|
||||||
|
Offset focalPoint;
|
||||||
|
Offset localFocalPoint;
|
||||||
|
double scaleChange;
|
||||||
|
Velocity currentVelocity;
|
||||||
|
bool calledStart;
|
||||||
|
await tester.pumpWidget(
|
||||||
|
MaterialApp(
|
||||||
|
home: Scaffold(
|
||||||
|
body: Center(
|
||||||
|
child: InteractiveViewer(
|
||||||
|
transformationController: transformationController,
|
||||||
|
onInteractionStart: (ScaleStartDetails details){
|
||||||
|
calledStart = true;
|
||||||
|
},
|
||||||
|
onInteractionUpdate: (ScaleUpdateDetails details){
|
||||||
|
scaleChange = details.scale;
|
||||||
|
focalPoint = details.focalPoint;
|
||||||
|
localFocalPoint = details.localFocalPoint;
|
||||||
|
},
|
||||||
|
onInteractionEnd: (ScaleEndDetails details){
|
||||||
|
currentVelocity = details.velocity;
|
||||||
|
},
|
||||||
|
child: Container(width: 200.0, height: 200.0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
final Offset center = tester.getCenter(find.byType(InteractiveViewer));
|
||||||
|
final Offset offCenter = Offset(center.dx - 20.0, center.dy - 20.0);
|
||||||
|
await scrollAt(offCenter, tester, const Offset(0.0, -20.0));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
const Velocity noMovement = Velocity(pixelsPerSecond: Offset(0,0));
|
||||||
|
final double afterScaling = transformationController.value.getMaxScaleOnAxis();
|
||||||
|
|
||||||
|
expect(scaleChange, greaterThan(1.0));
|
||||||
|
expect(afterScaling, scaleChange);
|
||||||
|
expect(currentVelocity, equals(noMovement));
|
||||||
|
expect(calledStart, equals(true));
|
||||||
|
// Focal points are given in coordinates outside of InteractiveViewer,
|
||||||
|
// with local being in relation to the viewport.
|
||||||
|
expect(focalPoint, offCenter);
|
||||||
|
expect(localFocalPoint, const Offset(80, 80));
|
||||||
|
|
||||||
|
// The top left corner of the viewport is not at the top left corner of
|
||||||
|
// the scene.
|
||||||
|
final Offset scenePoint = transformationController.toScene(Offset.zero);
|
||||||
|
expect(scenePoint.dx, greaterThan(0.0));
|
||||||
|
expect(scenePoint.dy, greaterThan(0.0));
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('viewport changes size', (WidgetTester tester) async {
|
testWidgets('viewport changes size', (WidgetTester tester) async {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user