Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix double tapping in gallery #439

Closed
wants to merge 3 commits into from
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 88 additions & 18 deletions lib/src/core/photo_view_gesture_detector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class PhotoViewGestureDetector extends StatelessWidget {
final scope = PhotoViewGestureDetectorScope.of(context);

final Axis? axis = scope?.axis;
final touchSlopFactor = scope?.touchSlopFactor;

final Map<Type, GestureRecognizerFactory> gestures =
<Type, GestureRecognizerFactory>{};
Expand Down Expand Up @@ -63,7 +64,10 @@ class PhotoViewGestureDetector extends StatelessWidget {
gestures[PhotoViewGestureRecognizer] =
GestureRecognizerFactoryWithHandlers<PhotoViewGestureRecognizer>(
() => PhotoViewGestureRecognizer(
hitDetector: hitDetector, debugOwner: this, validateAxis: axis),
hitDetector: hitDetector,
debugOwner: this,
validateAxis: axis,
touchSlopFactor: touchSlopFactor),
(PhotoViewGestureRecognizer instance) {
instance
..onStart = onScaleStart
Expand All @@ -85,22 +89,28 @@ class PhotoViewGestureRecognizer extends ScaleGestureRecognizer {
this.hitDetector,
Object? debugOwner,
this.validateAxis,
this.touchSlopFactor = 2,
PointerDeviceKind? kind,
}) : super(debugOwner: debugOwner, kind: kind);
final HitCornersDetector? hitDetector;
final 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);
Expand All @@ -115,25 +125,29 @@ class PhotoViewGestureRecognizer extends ScaleGestureRecognizer {
@override
void handleEvent(PointerEvent event) {
if (validateAxis != null) {
_computeEvent(event);
bool 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();
_decideIfWeAcceptEvent(event);
}
super.handleEvent(event);
}

void _computeEvent(PointerEvent event) {
if (event is PointerMoveEvent) {
if (!event.synthesized) {
_pointerLocations[event.pointer] = event.position;
if (didChangeConfiguration) {
_initialFocalPoint = _currentFocalPoint;
_initialSpan = _currentSpan;
}
} else if (event is PointerDownEvent) {
_pointerLocations[event.pointer] = event.position;
} else if (event is PointerUpEvent || event is PointerCancelEvent) {
_pointerLocations.remove(event.pointer);
}

_initialFocalPoint = _currentFocalPoint;
_decideIfWeAcceptEvent(event);
}
super.handleEvent(event);
}

void _updateDistances() {
Expand All @@ -143,6 +157,16 @@ class PhotoViewGestureRecognizer extends ScaleGestureRecognizer {
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.
double totalDeviation = 0.0;
for (final int pointer in _pointerLocations.keys) {
totalDeviation +=
(_currentFocalPoint! - _pointerLocations[pointer]!).distance;
}
_currentSpan = count > 0 ? totalDeviation / count : 0.0;
}

void _decideIfWeAcceptEvent(PointerEvent event) {
Expand All @@ -152,7 +176,18 @@ class PhotoViewGestureRecognizer extends ScaleGestureRecognizer {
final move = _initialFocalPoint! - _currentFocalPoint!;
final bool shouldMove = hitDetector!.shouldMove(move, validateAxis!);
if (shouldMove || _pointerLocations.keys.length > 1) {
acceptGesture(event.pointer);
final double spanDelta = (_currentSpan! - _initialSpan!).abs();
final double focalPointDelta =
(_currentFocalPoint! - _initialFocalPoint!).distance;
// warning: do not compare `focalPointDelta` to `kPanSlop`
// `ScaleGestureRecognizer` uses `kPanSlop`, but `HorizontalDragGestureRecognizer` uses `kTouchSlop`
// and PhotoView 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);
}
}
}
}
Expand All @@ -177,6 +212,7 @@ class PhotoViewGestureRecognizer extends ScaleGestureRecognizer {
class PhotoViewGestureDetectorScope extends InheritedWidget {
PhotoViewGestureDetectorScope({
this.axis,
this.touchSlopFactor = 0.2,
required Widget child,
}) : super(child: child);

Expand All @@ -188,8 +224,42 @@ class PhotoViewGestureDetectorScope extends InheritedWidget {

final Axis? axis;

// in [0, 1]
// 0: most reactive but will not let tap recognizers accept gestures
// <1: less reactive but gives the most leeway to other recognizers
// 1: will not be able to compete with a `HorizontalDragGestureRecognizer` up the widget tree
final double touchSlopFactor;

@override
bool updateShouldNotify(PhotoViewGestureDetectorScope oldWidget) {
return axis != oldWidget.axis;
return axis != oldWidget.axis &&
touchSlopFactor != oldWidget.touchSlopFactor;
}
}

// `PageView` contains a `Scrollable` which sets up a `HorizontalDragGestureRecognizer`
// this recognizer will win in the gesture arena when the drag distance reaches `kTouchSlop`
// we cannot change that, but we can prevent the scrollable from panning until this threshold is reached
// and let other recognizers accept the gesture instead
class PhotoViewPageViewScrollPhysics extends ScrollPhysics {
const PhotoViewPageViewScrollPhysics({
this.touchSlopFactor = 1,
ScrollPhysics? parent,
}) : super(parent: parent);

// in [0, 1]
// 0: most reactive but will not let PhotoView recognizers accept gestures
// 1: less reactive but gives the most leeway to PhotoView recognizers
final double touchSlopFactor;

@override
PhotoViewPageViewScrollPhysics applyTo(ScrollPhysics? ancestor) {
return PhotoViewPageViewScrollPhysics(
touchSlopFactor: touchSlopFactor,
parent: buildParent(ancestor),
);
}

@override
double get dragStartDistanceMotionThreshold => kTouchSlop * touchSlopFactor;
}