viewer: quick scale
This commit is contained in:
parent
07b9db6750
commit
e914188917
3 changed files with 189 additions and 158 deletions
|
@ -38,8 +38,9 @@ class MagnifierCore extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class MagnifierCoreState extends State<MagnifierCore> with TickerProviderStateMixin, MagnifierControllerDelegate, CornerHitDetector {
|
class MagnifierCoreState extends State<MagnifierCore> with TickerProviderStateMixin, MagnifierControllerDelegate, CornerHitDetector {
|
||||||
Offset _prevViewportFocalPosition;
|
Offset _startFocalPoint, _lastViewportFocalPosition;
|
||||||
double _gestureStartScale;
|
double _startScale, _quickScaleLastY, _quickScaleLastDistance;
|
||||||
|
bool _doubleTap, _quickScaleMoved;
|
||||||
|
|
||||||
AnimationController _scaleAnimationController;
|
AnimationController _scaleAnimationController;
|
||||||
Animation<double> _scaleAnimation;
|
Animation<double> _scaleAnimation;
|
||||||
|
@ -57,18 +58,40 @@ class MagnifierCoreState extends State<MagnifierCore> with TickerProviderStateMi
|
||||||
controller.update(position: _positionAnimation.value, source: ChangeSource.animation);
|
controller.update(position: _positionAnimation.value, source: ChangeSource.animation);
|
||||||
}
|
}
|
||||||
|
|
||||||
void onScaleStart(ScaleStartDetails details) {
|
void onScaleStart(ScaleStartDetails details, bool doubleTap) {
|
||||||
_gestureStartScale = scale;
|
_startScale = scale;
|
||||||
_prevViewportFocalPosition = details.localFocalPoint;
|
_startFocalPoint = details.localFocalPoint;
|
||||||
|
_lastViewportFocalPosition = _startFocalPoint;
|
||||||
|
_doubleTap = doubleTap;
|
||||||
|
_quickScaleLastDistance = null;
|
||||||
|
_quickScaleLastY = _startFocalPoint.dy;
|
||||||
|
_quickScaleMoved = false;
|
||||||
|
|
||||||
_scaleAnimationController.stop();
|
_scaleAnimationController.stop();
|
||||||
_positionAnimationController.stop();
|
_positionAnimationController.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
void onScaleUpdate(ScaleUpdateDetails details) {
|
void onScaleUpdate(ScaleUpdateDetails details) {
|
||||||
final newScale = _gestureStartScale * details.scale;
|
double newScale;
|
||||||
final panPositionDelta = details.focalPoint - _prevViewportFocalPosition;
|
if (_doubleTap) {
|
||||||
final scalePositionDelta = scaleBoundaries.viewportToStatePosition(controller, details.focalPoint) * (scale / newScale - 1);
|
// quick scale, aka one finger zoom
|
||||||
|
// magic numbers from `davemorrissey/subsampling-scale-image-view`
|
||||||
|
final focalPointY = details.focalPoint.dy;
|
||||||
|
final distance = (focalPointY - _startFocalPoint.dy).abs() * 2 + 20;
|
||||||
|
_quickScaleLastDistance ??= distance;
|
||||||
|
final spanDiff = (1 - (distance / _quickScaleLastDistance)).abs() * .5;
|
||||||
|
_quickScaleMoved |= spanDiff > .03;
|
||||||
|
final factor = _quickScaleMoved ? (focalPointY > _quickScaleLastY ? (1 + spanDiff) : (1 - spanDiff)) : 1;
|
||||||
|
_quickScaleLastDistance = distance;
|
||||||
|
_quickScaleLastY = focalPointY;
|
||||||
|
newScale = scale * factor;
|
||||||
|
} else {
|
||||||
|
newScale = _startScale * details.scale;
|
||||||
|
}
|
||||||
|
final scaleFocalPoint = _doubleTap ? _startFocalPoint : details.focalPoint;
|
||||||
|
|
||||||
|
final panPositionDelta = scaleFocalPoint - _lastViewportFocalPosition;
|
||||||
|
final scalePositionDelta = scaleBoundaries.viewportToStatePosition(controller, scaleFocalPoint) * (scale / newScale - 1);
|
||||||
final newPosition = position + panPositionDelta + scalePositionDelta;
|
final newPosition = position + panPositionDelta + scalePositionDelta;
|
||||||
|
|
||||||
updateScaleStateFromNewScale(newScale, ChangeSource.gesture);
|
updateScaleStateFromNewScale(newScale, ChangeSource.gesture);
|
||||||
|
@ -78,7 +101,7 @@ class MagnifierCoreState extends State<MagnifierCore> with TickerProviderStateMi
|
||||||
source: ChangeSource.gesture,
|
source: ChangeSource.gesture,
|
||||||
);
|
);
|
||||||
|
|
||||||
_prevViewportFocalPosition = details.focalPoint;
|
_lastViewportFocalPosition = scaleFocalPoint;
|
||||||
}
|
}
|
||||||
|
|
||||||
void onScaleEnd(ScaleEndDetails details) {
|
void onScaleEnd(ScaleEndDetails details) {
|
||||||
|
@ -116,7 +139,7 @@ class MagnifierCoreState extends State<MagnifierCore> with TickerProviderStateMi
|
||||||
final magnitude = details.velocity.pixelsPerSecond.distance;
|
final magnitude = details.velocity.pixelsPerSecond.distance;
|
||||||
|
|
||||||
// animate velocity only if there is no scale change and a significant magnitude
|
// animate velocity only if there is no scale change and a significant magnitude
|
||||||
if (_gestureStartScale / _scale == 1.0 && magnitude >= 400.0) {
|
if (_startScale / _scale == 1.0 && magnitude >= 400.0) {
|
||||||
final direction = details.velocity.pixelsPerSecond / magnitude;
|
final direction = details.velocity.pixelsPerSecond / magnitude;
|
||||||
animatePosition(
|
animatePosition(
|
||||||
_position,
|
_position,
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
import 'dart:math';
|
import 'package:aves/widgets/common/magnifier/core/scale_gesture_recognizer.dart';
|
||||||
|
|
||||||
import 'package:aves/widgets/common/magnifier/pan/gesture_detector_scope.dart';
|
import 'package:aves/widgets/common/magnifier/pan/gesture_detector_scope.dart';
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/widgets.dart';
|
import 'package:flutter/widgets.dart';
|
||||||
|
@ -21,7 +20,7 @@ class MagnifierGestureDetector extends StatefulWidget {
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final CornerHitDetector hitDetector;
|
final CornerHitDetector hitDetector;
|
||||||
final GestureScaleStartCallback onScaleStart;
|
final void Function(ScaleStartDetails details, bool doubleTap) onScaleStart;
|
||||||
final GestureScaleUpdateCallback onScaleUpdate;
|
final GestureScaleUpdateCallback onScaleUpdate;
|
||||||
final GestureScaleEndCallback onScaleEnd;
|
final GestureScaleEndCallback onScaleEnd;
|
||||||
|
|
||||||
|
@ -37,7 +36,7 @@ class MagnifierGestureDetector extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MagnifierGestureDetectorState extends State<MagnifierGestureDetector> {
|
class _MagnifierGestureDetectorState extends State<MagnifierGestureDetector> {
|
||||||
TapDownDetails doubleTapDetails;
|
final ValueNotifier<TapDownDetails> doubleTapDetails = ValueNotifier(null);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
@ -65,23 +64,23 @@ class _MagnifierGestureDetectorState extends State<MagnifierGestureDetector> {
|
||||||
debugOwner: this,
|
debugOwner: this,
|
||||||
validateAxis: axis,
|
validateAxis: axis,
|
||||||
touchSlopFactor: touchSlopFactor,
|
touchSlopFactor: touchSlopFactor,
|
||||||
|
doubleTapDetails: doubleTapDetails,
|
||||||
),
|
),
|
||||||
(instance) {
|
(instance) {
|
||||||
instance
|
instance.onStart = (details) => widget.onScaleStart(details, doubleTapDetails.value != null);
|
||||||
..onStart = widget.onScaleStart
|
instance.onUpdate = widget.onScaleUpdate;
|
||||||
..onUpdate = widget.onScaleUpdate
|
instance.onEnd = widget.onScaleEnd;
|
||||||
..onEnd = widget.onScaleEnd;
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
|
gestures[DoubleTapGestureRecognizer] = GestureRecognizerFactoryWithHandlers<DoubleTapGestureRecognizer>(
|
||||||
() => DoubleTapGestureRecognizer(debugOwner: this),
|
() => DoubleTapGestureRecognizer(debugOwner: this),
|
||||||
(instance) {
|
(instance) {
|
||||||
instance.onDoubleTapCancel = () => doubleTapDetails = null;
|
instance.onDoubleTapCancel = () => doubleTapDetails.value = null;
|
||||||
instance.onDoubleTapDown = (details) => doubleTapDetails = details;
|
instance.onDoubleTapDown = (details) => doubleTapDetails.value = details;
|
||||||
instance.onDoubleTap = () {
|
instance.onDoubleTap = () {
|
||||||
widget.onDoubleTap(doubleTapDetails);
|
widget.onDoubleTap(doubleTapDetails.value);
|
||||||
doubleTapDetails = null;
|
doubleTapDetails.value = null;
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -93,139 +92,3 @@ class _MagnifierGestureDetectorState extends State<MagnifierGestureDetector> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MagnifierGestureRecognizer extends ScaleGestureRecognizer {
|
|
||||||
MagnifierGestureRecognizer({
|
|
||||||
this.hitDetector,
|
|
||||||
Object debugOwner,
|
|
||||||
this.validateAxis,
|
|
||||||
this.touchSlopFactor = 2,
|
|
||||||
PointerDeviceKind kind,
|
|
||||||
}) : super(debugOwner: debugOwner, kind: kind);
|
|
||||||
final CornerHitDetector hitDetector;
|
|
||||||
final List<Axis> validateAxis;
|
|
||||||
final double touchSlopFactor;
|
|
||||||
|
|
||||||
Map<int, Offset> _pointerLocations = <int, Offset>{};
|
|
||||||
|
|
||||||
Offset _initialFocalPoint;
|
|
||||||
Offset _currentFocalPoint;
|
|
||||||
double _initialSpan;
|
|
||||||
double _currentSpan;
|
|
||||||
|
|
||||||
bool ready = true;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void addAllowedPointer(PointerEvent event) {
|
|
||||||
if (ready) {
|
|
||||||
ready = false;
|
|
||||||
_initialSpan = 0.0;
|
|
||||||
_currentSpan = 0.0;
|
|
||||||
_pointerLocations = <int, Offset>{};
|
|
||||||
}
|
|
||||||
super.addAllowedPointer(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void didStopTrackingLastPointer(int pointer) {
|
|
||||||
ready = true;
|
|
||||||
super.didStopTrackingLastPointer(pointer);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
void handleEvent(PointerEvent event) {
|
|
||||||
if (validateAxis != null && validateAxis.isNotEmpty) {
|
|
||||||
var didChangeConfiguration = false;
|
|
||||||
if (event is PointerMoveEvent) {
|
|
||||||
if (!event.synthesized) {
|
|
||||||
_pointerLocations[event.pointer] = event.position;
|
|
||||||
}
|
|
||||||
} else if (event is PointerDownEvent) {
|
|
||||||
_pointerLocations[event.pointer] = event.position;
|
|
||||||
didChangeConfiguration = true;
|
|
||||||
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
|
|
||||||
_pointerLocations.remove(event.pointer);
|
|
||||||
didChangeConfiguration = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
_updateDistances();
|
|
||||||
|
|
||||||
if (didChangeConfiguration) {
|
|
||||||
// cf super._reconfigure
|
|
||||||
_initialFocalPoint = _currentFocalPoint;
|
|
||||||
_initialSpan = _currentSpan;
|
|
||||||
}
|
|
||||||
|
|
||||||
_decideIfWeAcceptEvent(event);
|
|
||||||
}
|
|
||||||
super.handleEvent(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _updateDistances() {
|
|
||||||
// cf super._update
|
|
||||||
final count = _pointerLocations.keys.length;
|
|
||||||
|
|
||||||
// Compute the focal point
|
|
||||||
var focalPoint = Offset.zero;
|
|
||||||
for (final pointer in _pointerLocations.keys) {
|
|
||||||
focalPoint += _pointerLocations[pointer];
|
|
||||||
}
|
|
||||||
_currentFocalPoint = count > 0 ? focalPoint / count.toDouble() : Offset.zero;
|
|
||||||
|
|
||||||
// Span is the average deviation from focal point. Horizontal and vertical
|
|
||||||
// spans are the average deviations from the focal point's horizontal and
|
|
||||||
// vertical coordinates, respectively.
|
|
||||||
var totalDeviation = 0.0;
|
|
||||||
for (final pointer in _pointerLocations.keys) {
|
|
||||||
totalDeviation += (_currentFocalPoint - _pointerLocations[pointer]).distance;
|
|
||||||
}
|
|
||||||
_currentSpan = count > 0 ? totalDeviation / count : 0.0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void _decideIfWeAcceptEvent(PointerEvent event) {
|
|
||||||
if (!(event is PointerMoveEvent)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (_pointerLocations.keys.length >= 2) {
|
|
||||||
// when there are multiple pointers, we always accept the gesture to scale
|
|
||||||
// as this is not competing with single taps or other drag gestures
|
|
||||||
acceptGesture(event.pointer);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
final move = _initialFocalPoint - _currentFocalPoint;
|
|
||||||
var shouldMove = false;
|
|
||||||
if (validateAxis.length == 2) {
|
|
||||||
// the image is the descendant of gesture detector(s) handling drag in both directions
|
|
||||||
final shouldMoveX = validateAxis.contains(Axis.horizontal) && hitDetector.shouldMoveX(move);
|
|
||||||
final shouldMoveY = validateAxis.contains(Axis.vertical) && hitDetector.shouldMoveY(move);
|
|
||||||
if (shouldMoveX == shouldMoveY) {
|
|
||||||
// consistently can/cannot pan the image in both direction the same way
|
|
||||||
shouldMove = shouldMoveX;
|
|
||||||
} else {
|
|
||||||
// can pan the image in one direction, but should yield to an ascendant gesture detector in the other one
|
|
||||||
final d = move.direction;
|
|
||||||
// the gesture direction angle is in ]-pi, pi], cf `Offset` doc for details
|
|
||||||
final xPan = (-pi / 4 < d && d < pi / 4) || (3 / 4 * pi < d && d <= pi) || (-pi < d && d < -3 / 4 * pi);
|
|
||||||
final yPan = (pi / 4 < d && d < 3 / 4 * pi) || (-3 / 4 * pi < d && d < -pi / 4);
|
|
||||||
shouldMove = (xPan && shouldMoveX) || (yPan && shouldMoveY);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// the image is the descendant of a gesture detector handling drag in one direction
|
|
||||||
shouldMove = validateAxis.contains(Axis.vertical) ? hitDetector.shouldMoveY(move) : hitDetector.shouldMoveX(move);
|
|
||||||
}
|
|
||||||
if (shouldMove) {
|
|
||||||
final spanDelta = (_currentSpan - _initialSpan).abs();
|
|
||||||
final focalPointDelta = (_currentFocalPoint - _initialFocalPoint).distance;
|
|
||||||
// warning: do not compare `focalPointDelta` to `kPanSlop`
|
|
||||||
// `ScaleGestureRecognizer` uses `kPanSlop`, but `HorizontalDragGestureRecognizer` uses `kTouchSlop`
|
|
||||||
// and the magnifier recognizer may compete with the `HorizontalDragGestureRecognizer` from a containing `PageView`
|
|
||||||
// setting `touchSlopFactor` to 2 restores default `ScaleGestureRecognizer` behaviour as `kPanSlop = kTouchSlop * 2.0`
|
|
||||||
// setting `touchSlopFactor` in [0, 1] will allow this recognizer to accept the gesture before the one from `PageView`
|
|
||||||
if (spanDelta > kScaleSlop || focalPointDelta > kTouchSlop * touchSlopFactor) {
|
|
||||||
acceptGesture(event.pointer);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
145
lib/widgets/common/magnifier/core/scale_gesture_recognizer.dart
Normal file
145
lib/widgets/common/magnifier/core/scale_gesture_recognizer.dart
Normal file
|
@ -0,0 +1,145 @@
|
||||||
|
import 'dart:math';
|
||||||
|
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
|
import '../pan/corner_hit_detector.dart';
|
||||||
|
|
||||||
|
class MagnifierGestureRecognizer extends ScaleGestureRecognizer {
|
||||||
|
final CornerHitDetector hitDetector;
|
||||||
|
final List<Axis> validateAxis;
|
||||||
|
final double touchSlopFactor;
|
||||||
|
final ValueNotifier<TapDownDetails> doubleTapDetails;
|
||||||
|
|
||||||
|
MagnifierGestureRecognizer({
|
||||||
|
Object debugOwner,
|
||||||
|
PointerDeviceKind kind,
|
||||||
|
this.hitDetector,
|
||||||
|
this.validateAxis,
|
||||||
|
this.touchSlopFactor = 2,
|
||||||
|
this.doubleTapDetails,
|
||||||
|
}) : super(debugOwner: debugOwner, kind: kind);
|
||||||
|
|
||||||
|
Map<int, Offset> _pointerLocations = <int, Offset>{};
|
||||||
|
|
||||||
|
Offset _initialFocalPoint;
|
||||||
|
Offset _currentFocalPoint;
|
||||||
|
double _initialSpan;
|
||||||
|
double _currentSpan;
|
||||||
|
|
||||||
|
bool ready = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void addAllowedPointer(PointerEvent event) {
|
||||||
|
if (ready) {
|
||||||
|
ready = false;
|
||||||
|
_initialSpan = 0.0;
|
||||||
|
_currentSpan = 0.0;
|
||||||
|
_pointerLocations = <int, Offset>{};
|
||||||
|
}
|
||||||
|
super.addAllowedPointer(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didStopTrackingLastPointer(int pointer) {
|
||||||
|
ready = true;
|
||||||
|
super.didStopTrackingLastPointer(pointer);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void handleEvent(PointerEvent event) {
|
||||||
|
if (validateAxis != null && validateAxis.isNotEmpty) {
|
||||||
|
var didChangeConfiguration = false;
|
||||||
|
if (event is PointerMoveEvent) {
|
||||||
|
if (!event.synthesized) {
|
||||||
|
_pointerLocations[event.pointer] = event.position;
|
||||||
|
}
|
||||||
|
} else if (event is PointerDownEvent) {
|
||||||
|
_pointerLocations[event.pointer] = event.position;
|
||||||
|
didChangeConfiguration = true;
|
||||||
|
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
|
||||||
|
_pointerLocations.remove(event.pointer);
|
||||||
|
didChangeConfiguration = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
_updateDistances();
|
||||||
|
|
||||||
|
if (didChangeConfiguration) {
|
||||||
|
// cf super._reconfigure
|
||||||
|
_initialFocalPoint = _currentFocalPoint;
|
||||||
|
_initialSpan = _currentSpan;
|
||||||
|
}
|
||||||
|
|
||||||
|
_decideIfWeAcceptEvent(event);
|
||||||
|
}
|
||||||
|
super.handleEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
void _updateDistances() {
|
||||||
|
// cf super._update
|
||||||
|
final count = _pointerLocations.keys.length;
|
||||||
|
|
||||||
|
// Compute the focal point
|
||||||
|
var focalPoint = Offset.zero;
|
||||||
|
for (final pointer in _pointerLocations.keys) {
|
||||||
|
focalPoint += _pointerLocations[pointer];
|
||||||
|
}
|
||||||
|
_currentFocalPoint = count > 0 ? focalPoint / count.toDouble() : Offset.zero;
|
||||||
|
|
||||||
|
// Span is the average deviation from focal point. Horizontal and vertical
|
||||||
|
// spans are the average deviations from the focal point's horizontal and
|
||||||
|
// vertical coordinates, respectively.
|
||||||
|
var totalDeviation = 0.0;
|
||||||
|
for (final pointer in _pointerLocations.keys) {
|
||||||
|
totalDeviation += (_currentFocalPoint - _pointerLocations[pointer]).distance;
|
||||||
|
}
|
||||||
|
_currentSpan = count > 0 ? totalDeviation / count : 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void _decideIfWeAcceptEvent(PointerEvent event) {
|
||||||
|
if (!(event is PointerMoveEvent)) return;
|
||||||
|
|
||||||
|
if (_pointerLocations.keys.length >= 2) {
|
||||||
|
// when there are multiple pointers, we always accept the gesture to scale
|
||||||
|
// as this is not competing with single taps or other drag gestures
|
||||||
|
acceptGesture(event.pointer);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final move = _initialFocalPoint - _currentFocalPoint;
|
||||||
|
var shouldMove = false;
|
||||||
|
if (validateAxis.length == 2) {
|
||||||
|
// the image is the descendant of gesture detector(s) handling drag in both directions
|
||||||
|
final shouldMoveX = validateAxis.contains(Axis.horizontal) && hitDetector.shouldMoveX(move);
|
||||||
|
final shouldMoveY = validateAxis.contains(Axis.vertical) && hitDetector.shouldMoveY(move);
|
||||||
|
if (shouldMoveX == shouldMoveY) {
|
||||||
|
// consistently can/cannot pan the image in both direction the same way
|
||||||
|
shouldMove = shouldMoveX;
|
||||||
|
} else {
|
||||||
|
// can pan the image in one direction, but should yield to an ascendant gesture detector in the other one
|
||||||
|
final d = move.direction;
|
||||||
|
// the gesture direction angle is in ]-pi, pi], cf `Offset` doc for details
|
||||||
|
final xPan = (-pi / 4 < d && d < pi / 4) || (3 / 4 * pi < d && d <= pi) || (-pi < d && d < -3 / 4 * pi);
|
||||||
|
final yPan = (pi / 4 < d && d < 3 / 4 * pi) || (-3 / 4 * pi < d && d < -pi / 4);
|
||||||
|
shouldMove = (xPan && shouldMoveX) || (yPan && shouldMoveY);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// the image is the descendant of a gesture detector handling drag in one direction
|
||||||
|
shouldMove = validateAxis.contains(Axis.vertical) ? hitDetector.shouldMoveY(move) : hitDetector.shouldMoveX(move);
|
||||||
|
}
|
||||||
|
|
||||||
|
final doubleTap = doubleTapDetails?.value != null;
|
||||||
|
if (shouldMove || doubleTap) {
|
||||||
|
final spanDelta = (_currentSpan - _initialSpan).abs();
|
||||||
|
final focalPointDelta = (_currentFocalPoint - _initialFocalPoint).distance;
|
||||||
|
// warning: do not compare `focalPointDelta` to `kPanSlop`
|
||||||
|
// `ScaleGestureRecognizer` uses `kPanSlop`, but `HorizontalDragGestureRecognizer` uses `kTouchSlop`
|
||||||
|
// and the magnifier recognizer may compete with the `HorizontalDragGestureRecognizer` from a containing `PageView`
|
||||||
|
// setting `touchSlopFactor` to 2 restores default `ScaleGestureRecognizer` behaviour as `kPanSlop = kTouchSlop * 2.0`
|
||||||
|
// setting `touchSlopFactor` in [0, 1] will allow this recognizer to accept the gesture before the one from `PageView`
|
||||||
|
if (spanDelta > kScaleSlop || focalPointDelta > kTouchSlop * touchSlopFactor) {
|
||||||
|
acceptGesture(event.pointer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue