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.
|
||||
///
|
||||
/// 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}
|
||||
/// Will be called even if the interaction is disabled with
|
||||
/// [panEnabled] or [scaleEnabled].
|
||||
@ -229,10 +232,6 @@ class InteractiveViewer extends StatefulWidget {
|
||||
/// [GestureDetector.onScaleEnd]. Use [onInteractionStart],
|
||||
/// [onInteractionUpdate], and [onInteractionEnd] to respond to those
|
||||
/// 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}
|
||||
///
|
||||
/// See also:
|
||||
@ -243,8 +242,17 @@ class InteractiveViewer extends StatefulWidget {
|
||||
|
||||
/// 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}
|
||||
///
|
||||
/// 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:
|
||||
///
|
||||
/// * [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.
|
||||
///
|
||||
/// 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}
|
||||
///
|
||||
/// 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:
|
||||
///
|
||||
/// * [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
|
||||
// with GestureDetector's scale gesture.
|
||||
void _onScaleStart(ScaleStartDetails details) {
|
||||
if (widget.onInteractionStart != null) {
|
||||
widget.onInteractionStart!(details);
|
||||
}
|
||||
widget.onInteractionStart?.call(details);
|
||||
|
||||
if (_controller.isAnimating) {
|
||||
_controller.stop();
|
||||
@ -735,16 +750,6 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
||||
// handled with GestureDetector's scale gesture.
|
||||
void _onScaleUpdate(ScaleUpdateDetails details) {
|
||||
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(
|
||||
details.localFocalPoint,
|
||||
);
|
||||
@ -798,7 +803,7 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
||||
if (_round(_referenceFocalPoint!) != _round(focalPointSceneCheck)) {
|
||||
_referenceFocalPoint = focalPointSceneCheck;
|
||||
}
|
||||
return;
|
||||
break;
|
||||
|
||||
case _GestureType.rotate:
|
||||
if (details.rotation == 0.0) {
|
||||
@ -811,7 +816,7 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
||||
details.localFocalPoint,
|
||||
);
|
||||
_currentRotation = desiredRotation;
|
||||
return;
|
||||
break;
|
||||
|
||||
case _GestureType.pan:
|
||||
assert(_referenceFocalPoint != null);
|
||||
@ -832,16 +837,20 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
||||
_referenceFocalPoint = _transformationController!.toScene(
|
||||
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
|
||||
// are handled with GestureDetector's scale gesture.
|
||||
void _onScaleEnd(ScaleEndDetails details) {
|
||||
if (widget.onInteractionEnd != null) {
|
||||
widget.onInteractionEnd!(details);
|
||||
}
|
||||
widget.onInteractionEnd?.call(details);
|
||||
_scaleStart = null;
|
||||
_rotationStart = null;
|
||||
_referenceFocalPoint = null;
|
||||
@ -890,10 +899,17 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
||||
|
||||
// Handle mousewheel scroll events.
|
||||
void _receivedPointerSignal(PointerSignalEvent event) {
|
||||
if (event is PointerScrollEvent) {
|
||||
widget.onInteractionStart?.call(
|
||||
ScaleStartDetails(
|
||||
focalPoint: event.position,
|
||||
localFocalPoint: event.localPosition,
|
||||
),
|
||||
);
|
||||
if (!_gestureIsSupported(_GestureType.scale)) {
|
||||
widget.onInteractionEnd?.call(ScaleEndDetails());
|
||||
return;
|
||||
}
|
||||
if (event is PointerScrollEvent) {
|
||||
final RenderBox childRenderBox = _childKey.currentContext!.findRenderObject() as RenderBox;
|
||||
final Size childSize = childRenderBox.size;
|
||||
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(
|
||||
event.localPosition,
|
||||
);
|
||||
|
||||
_transformationController!.value = _matrixScale(
|
||||
_transformationController!.value,
|
||||
scaleChange,
|
||||
@ -917,22 +934,16 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
||||
_transformationController!.value,
|
||||
focalPointSceneScaled - focalPointScene,
|
||||
);
|
||||
if (widget.onInteractionStart != null) {
|
||||
widget.onInteractionStart!(
|
||||
ScaleStartDetails(focalPoint: focalPointSceneScaled)
|
||||
);
|
||||
}
|
||||
if (widget.onInteractionUpdate != null) {
|
||||
widget.onInteractionUpdate!(ScaleUpdateDetails(
|
||||
|
||||
widget.onInteractionUpdate?.call(ScaleUpdateDetails(
|
||||
focalPoint: event.position,
|
||||
localFocalPoint: event.localPosition,
|
||||
rotation: 0.0,
|
||||
scale: scaleChange,
|
||||
horizontalScale: 1.0,
|
||||
verticalScale: 1.0,
|
||||
));
|
||||
}
|
||||
if (widget.onInteractionEnd != null) {
|
||||
widget.onInteractionEnd!(ScaleEndDetails());
|
||||
}
|
||||
widget.onInteractionEnd?.call(ScaleEndDetails());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -625,6 +625,8 @@ void main() {
|
||||
|
||||
testWidgets('Scale with mouse returns onInteraction properties', (WidgetTester tester) async{
|
||||
final TransformationController transformationController = TransformationController();
|
||||
Offset focalPoint;
|
||||
Offset localFocalPoint;
|
||||
double scaleChange;
|
||||
Velocity currentVelocity;
|
||||
bool calledStart;
|
||||
@ -639,6 +641,8 @@ void main() {
|
||||
},
|
||||
onInteractionUpdate: (ScaleUpdateDetails details){
|
||||
scaleChange = details.scale;
|
||||
focalPoint = details.focalPoint;
|
||||
localFocalPoint = details.localFocalPoint;
|
||||
},
|
||||
onInteractionEnd: (ScaleEndDetails details){
|
||||
currentVelocity = details.velocity;
|
||||
@ -657,10 +661,72 @@ void main() {
|
||||
final double afterScaling = transformationController.value.getMaxScaleOnAxis();
|
||||
|
||||
expect(scaleChange, greaterThan(1.0));
|
||||
expect(afterScaling, isNot(equals(null)));
|
||||
expect(afterScaling, isNot(equals(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, 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 {
|
||||
|
Loading…
x
Reference in New Issue
Block a user