Unverified Commit 4aea2f99 authored by Frank Hinkel's avatar Frank Hinkel Committed by GitHub

Add friction coefficient to InteractiveViewer (#109443)

parent 8b36f4fb
......@@ -79,6 +79,7 @@ class InteractiveViewer extends StatefulWidget {
// use cases.
this.maxScale = 2.5,
this.minScale = 0.8,
this.interactionEndFrictionCoefficient = _kDrag,
this.onInteractionEnd,
this.onInteractionStart,
this.onInteractionUpdate,
......@@ -93,6 +94,7 @@ class InteractiveViewer extends StatefulWidget {
assert(constrained != null),
assert(minScale != null),
assert(minScale > 0),
assert(interactionEndFrictionCoefficient > 0),
assert(minScale.isFinite),
assert(maxScale != null),
assert(maxScale > 0),
......@@ -131,6 +133,7 @@ class InteractiveViewer extends StatefulWidget {
// use cases.
this.maxScale = 2.5,
this.minScale = 0.8,
this.interactionEndFrictionCoefficient = _kDrag,
this.onInteractionEnd,
this.onInteractionStart,
this.onInteractionUpdate,
......@@ -143,6 +146,7 @@ class InteractiveViewer extends StatefulWidget {
assert(builder != null),
assert(minScale != null),
assert(minScale > 0),
assert(interactionEndFrictionCoefficient > 0),
assert(minScale.isFinite),
assert(maxScale != null),
assert(maxScale > 0),
......@@ -326,6 +330,13 @@ class InteractiveViewer extends StatefulWidget {
/// than maxScale.
final double minScale;
/// Changes the deceleration behavior after a gesture.
///
/// Defaults to 0.0000135.
///
/// Cannot be null, and must be a finite number greater than zero.
final double interactionEndFrictionCoefficient;
/// Called when the user ends a pan or scale gesture on the widget.
///
/// At the time this is called, the [TransformationController] will have
......@@ -408,6 +419,10 @@ class InteractiveViewer extends StatefulWidget {
/// * [TextEditingController] for an example of another similar pattern.
final TransformationController? transformationController;
// Used as the coefficient of friction in the inertial translation animation.
// This value was eyeballed to give a feel similar to Google Photos.
static const double _kDrag = 0.0000135;
/// Returns the closest point to the given point on the given line segment.
@visibleForTesting
static Vector3 getNearestPointOnLine(Vector3 point, Vector3 l1, Vector3 l2) {
......@@ -549,10 +564,6 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
// https://github.com/flutter/flutter/issues/57698
final bool _rotateEnabled = false;
// Used as the coefficient of friction in the inertial translation animation.
// This value was eyeballed to give a feel similar to Google Photos.
static const double _kDrag = 0.0000135;
// The _boundaryRect is calculated by adding the boundaryMargin to the size of
// the child.
Rect get _boundaryRect {
......@@ -913,18 +924,18 @@ class _InteractiveViewerState extends State<InteractiveViewer> with TickerProvid
final Vector3 translationVector = _transformationController!.value.getTranslation();
final Offset translation = Offset(translationVector.x, translationVector.y);
final FrictionSimulation frictionSimulationX = FrictionSimulation(
_kDrag,
widget.interactionEndFrictionCoefficient,
translation.dx,
details.velocity.pixelsPerSecond.dx,
);
final FrictionSimulation frictionSimulationY = FrictionSimulation(
_kDrag,
widget.interactionEndFrictionCoefficient,
translation.dy,
details.velocity.pixelsPerSecond.dy,
);
final double tFinal = _getFinalTime(
details.velocity.pixelsPerSecond.distance,
_kDrag,
widget.interactionEndFrictionCoefficient,
);
_animation = Tween<Offset>(
begin: translation,
......
......@@ -1649,6 +1649,62 @@ void main() {
expect(scaleHighZoomedIn, greaterThan(scaleHighZoomedOut));
expect(scaleHighZoomedIn - scaleHighZoomedOut, lessThan(scaleZoomedIn - scaleZoomedOut));
});
testWidgets('interactionEndFrictionCoefficient', (WidgetTester tester) async {
// Use the default interactionEndFrictionCoefficient.
final TransformationController transformationController1 = TransformationController();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: SizedBox(
width: 200,
height: 200,
child: InteractiveViewer(
constrained: false,
transformationController: transformationController1,
child: const SizedBox(width: 2000.0, height: 2000.0),
),
),
),
),
);
expect(transformationController1.value, equals(Matrix4.identity()));
await tester.flingFrom(const Offset(100, 100), const Offset(0, -50), 100.0);
await tester.pumpAndSettle();
final Vector3 translation1 = transformationController1.value.getTranslation();
expect(translation1.y, lessThan(-58.0));
// Next try a custom interactionEndFrictionCoefficient.
final TransformationController transformationController2 = TransformationController();
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: SizedBox(
width: 200,
height: 200,
child: InteractiveViewer(
constrained: false,
interactionEndFrictionCoefficient: 0.01,
transformationController: transformationController2,
child: const SizedBox(width: 2000.0, height: 2000.0),
),
),
),
),
);
expect(transformationController2.value, equals(Matrix4.identity()));
await tester.flingFrom(const Offset(100, 100), const Offset(0, -50), 100.0);
await tester.pumpAndSettle();
final Vector3 translation2 = transformationController2.value.getTranslation();
// The coefficient 0.01 is greater than the default of 0.0000135,
// so the translation comes to a stop more quickly.
expect(translation2.y, lessThan(translation1.y));
});
});
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