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

Add onDrag Gestures #528

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
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
15 changes: 14 additions & 1 deletion lib/photo_view.dart
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
library photo_view;

import 'package:flutter/material.dart';

import 'package:photo_view/src/controller/photo_view_controller.dart';
import 'package:photo_view/src/controller/photo_view_scalestate_controller.dart';
import 'package:photo_view/src/core/photo_view_core.dart';
Expand Down Expand Up @@ -255,6 +254,7 @@ class PhotoView extends StatefulWidget {
this.onTapUp,
this.onTapDown,
this.onScaleEnd,
this.onDragEnd,
this.customSize,
this.gestureDetectorBehavior,
this.tightMode,
Expand Down Expand Up @@ -291,6 +291,7 @@ class PhotoView extends StatefulWidget {
this.onTapUp,
this.onTapDown,
this.onScaleEnd,
this.onDragEnd,
this.customSize,
this.gestureDetectorBehavior,
this.tightMode,
Expand Down Expand Up @@ -392,6 +393,10 @@ class PhotoView extends StatefulWidget {
/// particular location.
final PhotoViewImageScaleEndCallback? onScaleEnd;

/// A pointer that will trigger a drag has stopped contacting the screen at a
/// particular location.
final PhotoViewImageDragEndCallback? onDragEnd;

/// [HitTestBehavior] to be passed to the internal gesture detector.
final HitTestBehavior? gestureDetectorBehavior;

Expand Down Expand Up @@ -550,6 +555,7 @@ class _PhotoViewState extends State<PhotoView>
onTapUp: widget.onTapUp,
onTapDown: widget.onTapDown,
onScaleEnd: widget.onScaleEnd,
onDragEnd: widget.onDragEnd,
outerSize: computedOuterSize,
gestureDetectorBehavior: widget.gestureDetectorBehavior,
tightMode: widget.tightMode,
Expand Down Expand Up @@ -611,6 +617,13 @@ typedef PhotoViewImageScaleEndCallback = Function(
PhotoViewControllerValue controllerValue,
);

/// A type definition for a callback when a user finished vertical drag
typedef PhotoViewImageDragEndCallback = Function(
BuildContext context,
DragEndDetails details,
PhotoViewControllerValue controllerValue,
);

/// A type definition for a callback to show a widget while the image is loading, a [ImageChunkEvent] is passed to inform progress
typedef LoadingBuilder = Widget Function(
BuildContext context,
Expand Down
80 changes: 78 additions & 2 deletions lib/src/core/photo_view_core.dart
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import 'package:flutter/widgets.dart';
import 'package:photo_view/photo_view.dart'
show
PhotoViewScaleState,
PhotoViewHeroAttributes,
PhotoViewImageDragEndCallback,
PhotoViewImageScaleEndCallback,
PhotoViewImageTapDownCallback,
PhotoViewImageTapUpCallback,
PhotoViewImageScaleEndCallback,
PhotoViewScaleState,
ScaleStateCycle;
import 'package:photo_view/src/controller/photo_view_controller.dart';
import 'package:photo_view/src/controller/photo_view_controller_delegate.dart';
Expand All @@ -32,6 +33,7 @@ class PhotoViewCore extends StatefulWidget {
required this.onTapUp,
required this.onTapDown,
required this.onScaleEnd,
required this.onDragEnd,
required this.gestureDetectorBehavior,
required this.controller,
required this.scaleBoundaries,
Expand All @@ -54,6 +56,7 @@ class PhotoViewCore extends StatefulWidget {
this.onTapUp,
this.onTapDown,
this.onScaleEnd,
this.onDragEnd,
this.gestureDetectorBehavior,
required this.controller,
required this.scaleBoundaries,
Expand Down Expand Up @@ -87,6 +90,8 @@ class PhotoViewCore extends StatefulWidget {
final PhotoViewImageTapDownCallback? onTapDown;
final PhotoViewImageScaleEndCallback? onScaleEnd;

final PhotoViewImageDragEndCallback? onDragEnd;

final HitTestBehavior? gestureDetectorBehavior;
final bool tightMode;
final bool disableGestures;
Expand Down Expand Up @@ -146,6 +151,15 @@ class PhotoViewCoreState extends State<PhotoViewCore>
_rotationAnimationController.stop();
}

void onDragStart(DragStartDetails details) {
_rotationBefore = controller.rotation;
_scaleBefore = scale;
_normalizedPosition = details.globalPosition - controller.position;
_scaleAnimationController.stop();
_positionAnimationController.stop();
_rotationAnimationController.stop();
}

void onScaleUpdate(ScaleUpdateDetails details) {
final double newScale = _scaleBefore! * details.scale;
final Offset delta = details.focalPoint - _normalizedPosition!;
Expand All @@ -163,6 +177,19 @@ class PhotoViewCoreState extends State<PhotoViewCore>
);
}

void onDragUpdate(DragUpdateDetails details) {
final double newScale = _scaleBefore! * 1;
final Offset delta = details.globalPosition - _normalizedPosition!;

updateScaleStateFromNewScale(newScale);

updateMultiple(
scale: newScale,
position:
widget.enablePanAlways ? delta : clampPosition(position: delta * 1),
);
}

void onScaleEnd(ScaleEndDetails details) {
final double _scale = scale;
final Offset _position = controller.position;
Expand Down Expand Up @@ -209,6 +236,52 @@ class PhotoViewCoreState extends State<PhotoViewCore>
}
}

void onDragEnd(DragEndDetails details) {
final double _scale = scale;
final Offset _position = controller.position;
final double maxScale = scaleBoundaries.maxScale;
final double minScale = scaleBoundaries.minScale;

widget.onDragEnd?.call(context, details, controller.value);

//animate back to maxScale if gesture exceeded the maxScale specified
if (_scale > maxScale) {
final double scaleComebackRatio = maxScale / _scale;
animateScale(_scale, maxScale);
final Offset clampedPosition = clampPosition(
position: _position * scaleComebackRatio,
scale: maxScale,
);
animatePosition(_position, clampedPosition);
return;
}

//animate back to minScale if gesture fell smaller than the minScale specified
if (_scale < minScale) {
final double scaleComebackRatio = minScale / _scale;
animateScale(_scale, minScale);
animatePosition(
_position,
clampPosition(
position: _position * scaleComebackRatio,
scale: minScale,
),
);
return;
}
// get magnitude from gesture velocity
final double magnitude = details.velocity.pixelsPerSecond.distance;

// animate velocity only if there is no scale change and a significant magnitude
if (_scaleBefore! / _scale == 1.0 && magnitude >= 400.0) {
final Offset direction = details.velocity.pixelsPerSecond / magnitude;
animatePosition(
_position,
clampPosition(position: _position + direction * 100.0),
);
}
}

void onDoubleTap() {
nextScaleState();
}
Expand Down Expand Up @@ -350,6 +423,9 @@ class PhotoViewCoreState extends State<PhotoViewCore>
onScaleStart: onScaleStart,
onScaleUpdate: onScaleUpdate,
onScaleEnd: onScaleEnd,
onDragStart: onDragStart,
onDragUpdate: onDragUpdate,
onDragEnd: onDragEnd,
hitDetector: this,
onTapUp: widget.onTapUp != null
? (details) => widget.onTapUp!(context, details, value)
Expand Down
17 changes: 17 additions & 0 deletions lib/src/core/photo_view_gesture_detector.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@ class PhotoViewGestureDetector extends StatelessWidget {
this.onScaleStart,
this.onScaleUpdate,
this.onScaleEnd,
this.onDragStart,
this.onDragUpdate,
this.onDragEnd,
this.onDoubleTap,
this.child,
this.onTapUp,
Expand All @@ -24,6 +27,10 @@ class PhotoViewGestureDetector extends StatelessWidget {
final GestureScaleUpdateCallback? onScaleUpdate;
final GestureScaleEndCallback? onScaleEnd;

final GestureDragStartCallback? onDragStart;
final GestureDragUpdateCallback? onDragUpdate;
final GestureDragEndCallback? onDragEnd;

final GestureTapUpCallback? onTapUp;
final GestureTapDownCallback? onTapDown;

Expand Down Expand Up @@ -59,6 +66,16 @@ class PhotoViewGestureDetector extends StatelessWidget {
instance..onDoubleTap = onDoubleTap;
},
);
gestures[VerticalDragGestureRecognizer] =
GestureRecognizerFactoryWithHandlers<VerticalDragGestureRecognizer>(
() => VerticalDragGestureRecognizer(debugOwner: this),
(VerticalDragGestureRecognizer instance) {
instance
..onStart = onDragStart
..onUpdate = onDragUpdate
..onEnd = onDragEnd;
},
);

gestures[PhotoViewGestureRecognizer] =
GestureRecognizerFactoryWithHandlers<PhotoViewGestureRecognizer>(
Expand Down
3 changes: 3 additions & 0 deletions lib/src/photo_view_wrappers.dart
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ class ImageWrapper extends StatefulWidget {
required this.onTapUp,
required this.onTapDown,
required this.onScaleEnd,
required this.onDragEnd,
required this.outerSize,
required this.gestureDetectorBehavior,
required this.tightMode,
Expand Down Expand Up @@ -54,6 +55,7 @@ class ImageWrapper extends StatefulWidget {
final PhotoViewImageTapUpCallback? onTapUp;
final PhotoViewImageTapDownCallback? onTapDown;
final PhotoViewImageScaleEndCallback? onScaleEnd;
final PhotoViewImageDragEndCallback? onDragEnd;
final Size outerSize;
final HitTestBehavior? gestureDetectorBehavior;
final bool? tightMode;
Expand Down Expand Up @@ -196,6 +198,7 @@ class _ImageWrapperState extends State<ImageWrapper> {
onTapUp: widget.onTapUp,
onTapDown: widget.onTapDown,
onScaleEnd: widget.onScaleEnd,
onDragEnd: widget.onDragEnd,
gestureDetectorBehavior: widget.gestureDetectorBehavior,
tightMode: widget.tightMode ?? false,
filterQuality: widget.filterQuality ?? FilterQuality.none,
Expand Down