Skip to content

Instantly share code, notes, and snippets.

@klaszlo8207
Last active August 16, 2022 00:44
Show Gist options
  • Star 7 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save klaszlo8207/0df97fb3ba7484e20b0061076f3be842 to your computer and use it in GitHub Desktop.
Save klaszlo8207/0df97fb3ba7484e20b0061076f3be842 to your computer and use it in GitHub Desktop.
Flutter ScalingGestureDetector for a 3D view (you can pan and zoom in/out)
import 'package:advert_app/utils/logging.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/gestures.dart';
class ScalingGestureDetector extends StatefulWidget {
final Widget child;
final void Function(Offset initialPoint) onPanStart;
final void Function(Offset initialPoint, Offset delta) onPanUpdate;
final void Function() onPanEnd;
final void Function(Offset initialFocusPoint) onScaleStart;
final void Function(Offset changedFocusPoint, double scale) onScaleUpdate;
final void Function() onScaleEnd;
final void Function(double dx) onHorizontalDragUpdate;
final void Function(double dy) onVerticalDragUpdate;
ScalingGestureDetector({
this.child,
this.onPanStart,
this.onPanUpdate,
this.onPanEnd,
this.onScaleStart,
this.onScaleUpdate,
this.onScaleEnd,
this.onHorizontalDragUpdate,
this.onVerticalDragUpdate,
});
@override
_ScalingGestureDetectorState createState() => _ScalingGestureDetectorState();
}
class _ScalingGestureDetectorState extends State<ScalingGestureDetector> {
final List<Touch> _touches = [];
double _initialScalingDistance;
@override
Widget build(BuildContext context) {
return RawGestureDetector(
child: widget.child,
gestures: {
ImmediateMultiDragGestureRecognizer: GestureRecognizerFactoryWithHandlers<ImmediateMultiDragGestureRecognizer>(
() => ImmediateMultiDragGestureRecognizer(),
(ImmediateMultiDragGestureRecognizer instance) {
instance.onStart = (Offset offset) {
final touch = Touch(
offset,
(drag, details) => _onTouchUpdate(drag, details),
(drag, details) => _onTouchEnd(drag, details),
);
_onTouchStart(touch);
return touch;
};
},
),
},
);
}
void _onTouchStart(Touch touch) {
_touches.add(touch);
if (_touches.length == 1) {
if (widget.onPanStart != null) widget.onPanStart(touch._startOffset);
} else if (_touches.length == 2) {
_initialScalingDistance = (_touches[0]._currentOffset - _touches[1]._currentOffset).distance;
if (widget.onScaleStart != null) widget.onScaleStart((_touches[0]._currentOffset + _touches[1]._currentOffset) / 2);
} else {
// Do nothing/ ignore
}
}
final _DXY = 10;
void _onTouchUpdate(Touch touch, DragUpdateDetails details) {
assert(_touches.isNotEmpty);
touch._currentOffset = details.localPosition;
if (_touches.length == 1) {
if (widget.onPanUpdate != null) widget.onPanUpdate(touch._startOffset, details.localPosition - touch._startOffset);
if (widget.onHorizontalDragUpdate != null) {
final dx = (details.localPosition.dx - touch._startOffset.dx).abs();
if (dx > _DXY) widget.onHorizontalDragUpdate((details.localPosition.dx - touch._startOffset.dx).clamp(-2.0, 2.0));
}
if (widget.onVerticalDragUpdate != null) {
final dy = (details.localPosition.dy - touch._startOffset.dy).abs();
if (dy > _DXY) widget.onVerticalDragUpdate((details.localPosition.dy - touch._startOffset.dy).clamp(-2.0, 2.0));
}
} else {
// TODO average of ALL offsets, not only 2 first
var newDistance = (_touches[0]._currentOffset - _touches[1]._currentOffset).distance;
if (widget.onScaleUpdate != null) widget.onScaleUpdate((_touches[0]._currentOffset + _touches[1]._currentOffset) / 2, newDistance / _initialScalingDistance);
}
}
void _onTouchEnd(Touch touch, DragEndDetails details) {
_touches.remove(touch);
if (_touches.length == 0) {
if (widget.onPanEnd != null) widget.onPanEnd();
} else if (_touches.length == 1) {
if (widget.onScaleEnd != null) widget.onScaleEnd();
// Restart pan
_touches[0]._startOffset = _touches[0]._currentOffset;
if (widget.onPanStart != null) widget.onPanStart(_touches[0]._startOffset);
}
}
}
class Touch extends Drag {
Offset _startOffset;
Offset _currentOffset;
final void Function(Drag drag, DragUpdateDetails details) onUpdate;
final void Function(Drag drag, DragEndDetails details) onEnd;
Touch(this._startOffset, this.onUpdate, this.onEnd) {
_currentOffset = _startOffset;
}
@override
void update(DragUpdateDetails details) {
super.update(details);
onUpdate(this, details);
}
@override
void end(DragEndDetails details) {
super.end(details);
onEnd(this, details);
}
}
@duongtruong12
Copy link

i got reset offset to 0.0 everytime i drag widget again. Can you help me please

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment