mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
panningDirection parameter added to InteractiveViewer (#109014)
This commit is contained in:
parent
8074811c2f
commit
0ad0a56e86
@ -34,7 +34,7 @@ class MyStatelessWidget extends StatelessWidget {
|
||||
const int columnCount = 6;
|
||||
|
||||
return InteractiveViewer(
|
||||
alignPanAxis: true,
|
||||
panAxis: PanAxis.aligned,
|
||||
constrained: false,
|
||||
scaleEnabled: false,
|
||||
child: Table(
|
||||
|
@ -67,7 +67,12 @@ class InteractiveViewer extends StatefulWidget {
|
||||
InteractiveViewer({
|
||||
super.key,
|
||||
this.clipBehavior = Clip.hardEdge,
|
||||
@Deprecated(
|
||||
'Use panAxis instead. '
|
||||
'This feature was deprecated after v3.3.0-0.5.pre.',
|
||||
)
|
||||
this.alignPanAxis = false,
|
||||
this.panAxis = PanAxis.free,
|
||||
this.boundaryMargin = EdgeInsets.zero,
|
||||
this.constrained = true,
|
||||
// These default scale values were eyeballed as reasonable limits for common
|
||||
@ -83,6 +88,7 @@ class InteractiveViewer extends StatefulWidget {
|
||||
this.transformationController,
|
||||
required Widget this.child,
|
||||
}) : assert(alignPanAxis != null),
|
||||
assert(panAxis != null),
|
||||
assert(child != null),
|
||||
assert(constrained != null),
|
||||
assert(minScale != null),
|
||||
@ -114,7 +120,12 @@ class InteractiveViewer extends StatefulWidget {
|
||||
InteractiveViewer.builder({
|
||||
super.key,
|
||||
this.clipBehavior = Clip.hardEdge,
|
||||
@Deprecated(
|
||||
'Use panAxis instead. '
|
||||
'This feature was deprecated after v3.3.0-0.5.pre.',
|
||||
)
|
||||
this.alignPanAxis = false,
|
||||
this.panAxis = PanAxis.free,
|
||||
this.boundaryMargin = EdgeInsets.zero,
|
||||
// These default scale values were eyeballed as reasonable limits for common
|
||||
// use cases.
|
||||
@ -128,7 +139,7 @@ class InteractiveViewer extends StatefulWidget {
|
||||
this.scaleFactor = 200.0,
|
||||
this.transformationController,
|
||||
required InteractiveViewerWidgetBuilder this.builder,
|
||||
}) : assert(alignPanAxis != null),
|
||||
}) : assert(panAxis != null),
|
||||
assert(builder != null),
|
||||
assert(minScale != null),
|
||||
assert(minScale > 0),
|
||||
@ -158,6 +169,8 @@ class InteractiveViewer extends StatefulWidget {
|
||||
/// Defaults to [Clip.hardEdge].
|
||||
final Clip clipBehavior;
|
||||
|
||||
/// This property is deprecated, please use [panAxis] instead.
|
||||
///
|
||||
/// If true, panning is only allowed in the direction of the horizontal axis
|
||||
/// or the vertical axis.
|
||||
///
|
||||
@ -169,8 +182,25 @@ class InteractiveViewer extends StatefulWidget {
|
||||
/// See also:
|
||||
/// * [constrained], which has an example of creating a table that uses
|
||||
/// alignPanAxis.
|
||||
@Deprecated(
|
||||
'Use panAxis instead. '
|
||||
'This feature was deprecated after v3.3.0-0.5.pre.',
|
||||
)
|
||||
final bool alignPanAxis;
|
||||
|
||||
/// When set to [PanAxis.aligned], panning is only allowed in the horizontal
|
||||
/// axis or the vertical axis, diagonal panning is not allowed.
|
||||
///
|
||||
/// When set to [PanAxis.vertical] or [PanAxis.horizontal] panning is only
|
||||
/// allowed in the specified axis. For example, if set to [PanAxis.vertical],
|
||||
/// panning will only be allowed in the vertical axis. And if set to [PanAxis.horizontal],
|
||||
/// panning will only be allowed in the horizontal axis.
|
||||
///
|
||||
/// When set to [PanAxis.free] panning is allowed in all directions.
|
||||
///
|
||||
/// Defaults to [PanAxis.free].
|
||||
final PanAxis panAxis;
|
||||
|
||||
/// A margin for the visible boundaries of the child.
|
||||
///
|
||||
/// Any transformation that results in the viewport being able to view outside
|
||||
@ -507,7 +537,7 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
||||
final GlobalKey _parentKey = GlobalKey();
|
||||
Animation<Offset>? _animation;
|
||||
late AnimationController _controller;
|
||||
Axis? _panAxis; // Used with alignPanAxis.
|
||||
Axis? _currentAxis; // Used with panAxis.
|
||||
Offset? _referenceFocalPoint; // Point where the current gesture began.
|
||||
double? _scaleStart; // Scale value at start of scaling gesture.
|
||||
double? _rotationStart = 0.0; // Rotation at start of rotation gesture.
|
||||
@ -566,9 +596,26 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
||||
return matrix.clone();
|
||||
}
|
||||
|
||||
final Offset alignedTranslation = widget.alignPanAxis && _panAxis != null
|
||||
? _alignAxis(translation, _panAxis!)
|
||||
: translation;
|
||||
late final Offset alignedTranslation;
|
||||
|
||||
if (_currentAxis != null) {
|
||||
switch(widget.panAxis){
|
||||
case PanAxis.horizontal:
|
||||
alignedTranslation = _alignAxis(translation, Axis.horizontal);
|
||||
break;
|
||||
case PanAxis.vertical:
|
||||
alignedTranslation = _alignAxis(translation, Axis.vertical);
|
||||
break;
|
||||
case PanAxis.aligned:
|
||||
alignedTranslation = _alignAxis(translation, _currentAxis!);
|
||||
break;
|
||||
case PanAxis.free:
|
||||
alignedTranslation = translation;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
alignedTranslation = translation;
|
||||
}
|
||||
|
||||
final Matrix4 nextMatrix = matrix.clone()..translate(
|
||||
alignedTranslation.dx,
|
||||
@ -734,7 +781,7 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
||||
}
|
||||
|
||||
_gestureType = null;
|
||||
_panAxis = null;
|
||||
_currentAxis = null;
|
||||
_scaleStart = _transformationController!.value.getMaxScaleOnAxis();
|
||||
_referenceFocalPoint = _transformationController!.toScene(
|
||||
details.localFocalPoint,
|
||||
@ -825,7 +872,7 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
||||
widget.onInteractionUpdate?.call(details);
|
||||
return;
|
||||
}
|
||||
_panAxis ??= _getPanAxis(_referenceFocalPoint!, focalPointScene);
|
||||
_currentAxis ??= _getPanAxis(_referenceFocalPoint!, focalPointScene);
|
||||
// Translate so that the same point in the scene is underneath the
|
||||
// focal point before and after the movement.
|
||||
final Offset translationChange = focalPointScene - _referenceFocalPoint!;
|
||||
@ -853,13 +900,13 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
||||
_controller.reset();
|
||||
|
||||
if (!_gestureIsSupported(_gestureType)) {
|
||||
_panAxis = null;
|
||||
_currentAxis = null;
|
||||
return;
|
||||
}
|
||||
|
||||
// If the scale ended with enough velocity, animate inertial movement.
|
||||
if (_gestureType != _GestureType.pan || details.velocity.pixelsPerSecond.distance < kMinFlingVelocity) {
|
||||
_panAxis = null;
|
||||
_currentAxis = null;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -947,7 +994,7 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
|
||||
// Handle inertia drag animation.
|
||||
void _onAnimate() {
|
||||
if (!_controller.isAnimating) {
|
||||
_panAxis = null;
|
||||
_currentAxis = null;
|
||||
_animation?.removeListener(_onAnimate);
|
||||
_animation = null;
|
||||
_controller.reset();
|
||||
@ -1296,3 +1343,20 @@ Axis? _getPanAxis(Offset point1, Offset point2) {
|
||||
final double y = point2.dy - point1.dy;
|
||||
return x.abs() > y.abs() ? Axis.horizontal : Axis.vertical;
|
||||
}
|
||||
|
||||
/// This enum is used to specify the behavior of the [InteractiveViewer] when
|
||||
/// the user drags the viewport.
|
||||
enum PanAxis{
|
||||
/// The user can only pan the viewport along the horizontal axis.
|
||||
horizontal,
|
||||
|
||||
/// The user can only pan the viewport along the vertical axis.
|
||||
vertical,
|
||||
|
||||
/// The user can pan the viewport along the horizontal and vertical axes
|
||||
/// but not diagonally.
|
||||
aligned,
|
||||
|
||||
/// The user can pan the viewport freely in any direction.
|
||||
free,
|
||||
}
|
||||
|
@ -288,14 +288,52 @@ void main() {
|
||||
expect(transformationController.value.getMaxScaleOnAxis(), minScale);
|
||||
});
|
||||
|
||||
testWidgets('alignPanAxis allows panning in one direction only for diagonal gesture', (WidgetTester tester) async {
|
||||
testWidgets('PanAxis.free allows panning in all directions for diagonal gesture', (WidgetTester tester) async {
|
||||
final TransformationController transformationController = TransformationController();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: InteractiveViewer(
|
||||
alignPanAxis: true,
|
||||
boundaryMargin: const EdgeInsets.all(double.infinity),
|
||||
transformationController: transformationController,
|
||||
child: const SizedBox(width: 200.0, height: 200.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(transformationController.value, equals(Matrix4.identity()));
|
||||
|
||||
// Perform a diagonal drag gesture.
|
||||
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(childInterior);
|
||||
await tester.pump();
|
||||
await gesture.moveTo(childOffset);
|
||||
await tester.pump();
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Translation has only happened along the y axis (the default axis when
|
||||
// a gesture is perfectly at 45 degrees to the axes).
|
||||
final Vector3 translation = transformationController.value.getTranslation();
|
||||
expect(translation.x, childOffset.dx - childInterior.dx);
|
||||
expect(translation.y, childOffset.dy - childInterior.dy);
|
||||
});
|
||||
|
||||
testWidgets('PanAxis.aligned allows panning in one direction only for diagonal gesture', (WidgetTester tester) async {
|
||||
final TransformationController transformationController = TransformationController();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: InteractiveViewer(
|
||||
panAxis: PanAxis.aligned,
|
||||
boundaryMargin: const EdgeInsets.all(double.infinity),
|
||||
transformationController: transformationController,
|
||||
child: const SizedBox(width: 200.0, height: 200.0),
|
||||
@ -327,14 +365,14 @@ void main() {
|
||||
expect(translation.y, childOffset.dy - childInterior.dy);
|
||||
});
|
||||
|
||||
testWidgets('alignPanAxis allows panning in one direction only for horizontal leaning gesture', (WidgetTester tester) async {
|
||||
testWidgets('PanAxis.aligned allows panning in one direction only for horizontal leaning gesture', (WidgetTester tester) async {
|
||||
final TransformationController transformationController = TransformationController();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: InteractiveViewer(
|
||||
alignPanAxis: true,
|
||||
panAxis: PanAxis.aligned,
|
||||
boundaryMargin: const EdgeInsets.all(double.infinity),
|
||||
transformationController: transformationController,
|
||||
child: const SizedBox(width: 200.0, height: 200.0),
|
||||
@ -366,6 +404,240 @@ void main() {
|
||||
expect(translation.y, 0.0);
|
||||
});
|
||||
|
||||
testWidgets('PanAxis.horizontal allows panning in the horizontal direction only for diagonal gesture', (WidgetTester tester) async {
|
||||
final TransformationController transformationController = TransformationController();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: InteractiveViewer(
|
||||
panAxis: PanAxis.horizontal,
|
||||
boundaryMargin: const EdgeInsets.all(double.infinity),
|
||||
transformationController: transformationController,
|
||||
child: const SizedBox(width: 200.0, height: 200.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(transformationController.value, equals(Matrix4.identity()));
|
||||
|
||||
// Perform a diagonal drag gesture.
|
||||
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(childInterior);
|
||||
await tester.pump();
|
||||
await gesture.moveTo(childOffset);
|
||||
await tester.pump();
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Translation has only happened along the x axis (the default axis when
|
||||
// a gesture is perfectly at 45 degrees to the axes).
|
||||
final Vector3 translation = transformationController.value.getTranslation();
|
||||
expect(translation.x, childOffset.dx - childInterior.dx);
|
||||
expect(translation.y, 0.0);
|
||||
});
|
||||
|
||||
testWidgets('PanAxis.horizontal allows panning in the horizontal direction only for horizontal leaning gesture', (WidgetTester tester) async {
|
||||
final TransformationController transformationController = TransformationController();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: InteractiveViewer(
|
||||
panAxis: PanAxis.horizontal,
|
||||
boundaryMargin: const EdgeInsets.all(double.infinity),
|
||||
transformationController: transformationController,
|
||||
child: const SizedBox(width: 200.0, height: 200.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(transformationController.value, equals(Matrix4.identity()));
|
||||
|
||||
// Perform a horizontally leaning diagonal drag gesture.
|
||||
final Offset childOffset = tester.getTopLeft(find.byType(SizedBox));
|
||||
final Offset childInterior = Offset(
|
||||
childOffset.dx + 20.0,
|
||||
childOffset.dy + 10.0,
|
||||
);
|
||||
final TestGesture gesture = await tester.startGesture(childInterior);
|
||||
await tester.pump();
|
||||
await gesture.moveTo(childOffset);
|
||||
await tester.pump();
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Translation happened only along the x axis because that's the axis that
|
||||
// had been set to the panningDirection parameter.
|
||||
final Vector3 translation = transformationController.value.getTranslation();
|
||||
expect(translation.x, childOffset.dx - childInterior.dx);
|
||||
expect(translation.y, 0.0);
|
||||
});
|
||||
|
||||
testWidgets('PanAxis.horizontal does not allow panning in vertical direction on vertical gesture', (WidgetTester tester) async {
|
||||
final TransformationController transformationController = TransformationController();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: InteractiveViewer(
|
||||
panAxis: PanAxis.horizontal,
|
||||
boundaryMargin: const EdgeInsets.all(double.infinity),
|
||||
transformationController: transformationController,
|
||||
child: const SizedBox(width: 200.0, height: 200.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(transformationController.value, equals(Matrix4.identity()));
|
||||
|
||||
// Perform a horizontally leaning diagonal drag gesture.
|
||||
final Offset childOffset = tester.getTopLeft(find.byType(SizedBox));
|
||||
final Offset childInterior = Offset(
|
||||
childOffset.dx + 0.0,
|
||||
childOffset.dy + 10.0,
|
||||
);
|
||||
final TestGesture gesture = await tester.startGesture(childInterior);
|
||||
await tester.pump();
|
||||
await gesture.moveTo(childOffset);
|
||||
await tester.pump();
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Translation didn't happen because the only axis allowed to do panning
|
||||
// is the horizontal.
|
||||
final Vector3 translation = transformationController.value.getTranslation();
|
||||
expect(translation.x, 0.0);
|
||||
expect(translation.y, 0.0);
|
||||
});
|
||||
|
||||
testWidgets('PanAxis.vertical allows panning in the vertical direction only for diagonal gesture', (WidgetTester tester) async {
|
||||
final TransformationController transformationController = TransformationController();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: InteractiveViewer(
|
||||
panAxis: PanAxis.vertical,
|
||||
boundaryMargin: const EdgeInsets.all(double.infinity),
|
||||
transformationController: transformationController,
|
||||
child: const SizedBox(width: 200.0, height: 200.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(transformationController.value, equals(Matrix4.identity()));
|
||||
|
||||
// Perform a diagonal drag gesture.
|
||||
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(childInterior);
|
||||
await tester.pump();
|
||||
await gesture.moveTo(childOffset);
|
||||
await tester.pump();
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Translation has only happened along the x axis (the default axis when
|
||||
// a gesture is perfectly at 45 degrees to the axes).
|
||||
final Vector3 translation = transformationController.value.getTranslation();
|
||||
expect(translation.y, childOffset.dy - childInterior.dy);
|
||||
expect(translation.x, 0.0);
|
||||
});
|
||||
|
||||
testWidgets('PanAxis.vertical allows panning in the vertical direction only for vertical leaning gesture', (WidgetTester tester) async {
|
||||
final TransformationController transformationController = TransformationController();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: InteractiveViewer(
|
||||
panAxis: PanAxis.vertical,
|
||||
boundaryMargin: const EdgeInsets.all(double.infinity),
|
||||
transformationController: transformationController,
|
||||
child: const SizedBox(width: 200.0, height: 200.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(transformationController.value, equals(Matrix4.identity()));
|
||||
|
||||
// Perform a horizontally leaning diagonal drag gesture.
|
||||
final Offset childOffset = tester.getTopLeft(find.byType(SizedBox));
|
||||
final Offset childInterior = Offset(
|
||||
childOffset.dx + 20.0,
|
||||
childOffset.dy + 10.0,
|
||||
);
|
||||
final TestGesture gesture = await tester.startGesture(childInterior);
|
||||
await tester.pump();
|
||||
await gesture.moveTo(childOffset);
|
||||
await tester.pump();
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Translation happened only along the x axis because that's the axis that
|
||||
// had been set to the panningDirection parameter.
|
||||
final Vector3 translation = transformationController.value.getTranslation();
|
||||
expect(translation.y, childOffset.dy - childInterior.dy);
|
||||
expect(translation.x, 0.0);
|
||||
});
|
||||
|
||||
testWidgets('PanAxis.vertical does not allow panning in horizontal direction on vertical gesture', (WidgetTester tester) async {
|
||||
final TransformationController transformationController = TransformationController();
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: InteractiveViewer(
|
||||
panAxis: PanAxis.vertical,
|
||||
boundaryMargin: const EdgeInsets.all(double.infinity),
|
||||
transformationController: transformationController,
|
||||
child: const SizedBox(width: 200.0, height: 200.0),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(transformationController.value, equals(Matrix4.identity()));
|
||||
|
||||
// Perform a horizontally leaning diagonal drag gesture.
|
||||
final Offset childOffset = tester.getTopLeft(find.byType(SizedBox));
|
||||
final Offset childInterior = Offset(
|
||||
childOffset.dx + 10.0,
|
||||
childOffset.dy + 0.0,
|
||||
);
|
||||
final TestGesture gesture = await tester.startGesture(childInterior);
|
||||
await tester.pump();
|
||||
await gesture.moveTo(childOffset);
|
||||
await tester.pump();
|
||||
await gesture.up();
|
||||
await tester.pumpAndSettle();
|
||||
|
||||
// Translation didn't happen because the only axis allowed to do panning
|
||||
// is the horizontal.
|
||||
final Vector3 translation = transformationController.value.getTranslation();
|
||||
expect(translation.x, 0.0);
|
||||
expect(translation.y, 0.0);
|
||||
});
|
||||
|
||||
testWidgets('inertia fling and boundary sliding', (WidgetTester tester) async {
|
||||
final TransformationController transformationController = TransformationController();
|
||||
const double boundaryMargin = 50.0;
|
||||
@ -519,7 +791,7 @@ void main() {
|
||||
home: Scaffold(
|
||||
body: Center(
|
||||
child: InteractiveViewer(
|
||||
alignPanAxis: true,
|
||||
panAxis: PanAxis.aligned,
|
||||
boundaryMargin: const EdgeInsets.all(boundaryMargin),
|
||||
minScale: minScale,
|
||||
transformationController: transformationController,
|
||||
|
Loading…
Reference in New Issue
Block a user