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 { final List _touches = []; double _initialScalingDistance; @override Widget build(BuildContext context) { return RawGestureDetector( child: widget.child, gestures: { ImmediateMultiDragGestureRecognizer: GestureRecognizerFactoryWithHandlers( () => 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); } }