viewer: improved panning inertia
This commit is contained in:
parent
e914188917
commit
640bb272dd
1 changed files with 43 additions and 32 deletions
|
@ -19,6 +19,7 @@ class MagnifierCore extends StatefulWidget {
|
||||||
@required this.controller,
|
@required this.controller,
|
||||||
@required this.scaleStateCycle,
|
@required this.scaleStateCycle,
|
||||||
@required this.applyScale,
|
@required this.applyScale,
|
||||||
|
this.panInertia = .2,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
final Widget child;
|
final Widget child;
|
||||||
|
@ -30,6 +31,7 @@ class MagnifierCore extends StatefulWidget {
|
||||||
|
|
||||||
final HitTestBehavior gestureDetectorBehavior;
|
final HitTestBehavior gestureDetectorBehavior;
|
||||||
final bool applyScale;
|
final bool applyScale;
|
||||||
|
final double panInertia;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<StatefulWidget> createState() {
|
State<StatefulWidget> createState() {
|
||||||
|
@ -41,6 +43,7 @@ class MagnifierCoreState extends State<MagnifierCore> with TickerProviderStateMi
|
||||||
Offset _startFocalPoint, _lastViewportFocalPosition;
|
Offset _startFocalPoint, _lastViewportFocalPosition;
|
||||||
double _startScale, _quickScaleLastY, _quickScaleLastDistance;
|
double _startScale, _quickScaleLastY, _quickScaleLastDistance;
|
||||||
bool _doubleTap, _quickScaleMoved;
|
bool _doubleTap, _quickScaleMoved;
|
||||||
|
DateTime _lastScaleGestureDate;
|
||||||
|
|
||||||
AnimationController _scaleAnimationController;
|
AnimationController _scaleAnimationController;
|
||||||
Animation<double> _scaleAnimation;
|
Animation<double> _scaleAnimation;
|
||||||
|
@ -105,47 +108,55 @@ class MagnifierCoreState extends State<MagnifierCore> with TickerProviderStateMi
|
||||||
}
|
}
|
||||||
|
|
||||||
void onScaleEnd(ScaleEndDetails details) {
|
void onScaleEnd(ScaleEndDetails details) {
|
||||||
final _scale = scale;
|
|
||||||
final _position = controller.position;
|
final _position = controller.position;
|
||||||
|
final _scale = controller.scale;
|
||||||
final maxScale = scaleBoundaries.maxScale;
|
final maxScale = scaleBoundaries.maxScale;
|
||||||
final minScale = scaleBoundaries.minScale;
|
final minScale = scaleBoundaries.minScale;
|
||||||
|
|
||||||
//animate back to maxScale if gesture exceeded the maxScale specified
|
// animate back to min/max scale if gesture yielded a scale exceeding them
|
||||||
if (_scale > maxScale) {
|
if (_scale > maxScale || _scale < minScale) {
|
||||||
final scaleComebackRatio = maxScale / _scale;
|
final newScale = _scale.clamp(minScale, maxScale);
|
||||||
animateScale(_scale, maxScale);
|
final newPosition = clampPosition(position: _position * newScale / _scale, scale: newScale);
|
||||||
final clampedPosition = clampPosition(
|
animateScale(_scale, newScale);
|
||||||
position: _position * scaleComebackRatio,
|
animatePosition(_position, newPosition);
|
||||||
scale: maxScale,
|
|
||||||
);
|
|
||||||
animatePosition(_position, clampedPosition);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//animate back to minScale if gesture fell smaller than the minScale specified
|
// The gesture recognizer triggers a new `onScaleStart` every time a pointer/finger is added or removed.
|
||||||
if (_scale < minScale) {
|
// Following a pinch-to-zoom gesture, a new panning gesture may start if the user does not lift both fingers at the same time,
|
||||||
final scaleComebackRatio = minScale / _scale;
|
// so we dismiss such panning gestures when it looks like it followed a scaling gesture.
|
||||||
animateScale(_scale, minScale);
|
final isPanning = _scale == _startScale && (DateTime.now().difference(_lastScaleGestureDate)).inMilliseconds > 100;
|
||||||
animatePosition(
|
|
||||||
_position,
|
|
||||||
clampPosition(
|
|
||||||
position: _position * scaleComebackRatio,
|
|
||||||
scale: minScale,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
// get magnitude from gesture velocity
|
|
||||||
final magnitude = details.velocity.pixelsPerSecond.distance;
|
|
||||||
|
|
||||||
// animate velocity only if there is no scale change and a significant magnitude
|
// animate position only when panning without scaling
|
||||||
if (_startScale / _scale == 1.0 && magnitude >= 400.0) {
|
if (isPanning) {
|
||||||
final direction = details.velocity.pixelsPerSecond / magnitude;
|
final pps = details.velocity.pixelsPerSecond;
|
||||||
animatePosition(
|
if (pps != Offset.zero) {
|
||||||
_position,
|
final newPosition = clampPosition(position: _position + pps * widget.panInertia);
|
||||||
clampPosition(position: _position + direction * 100.0),
|
final tween = Tween<Offset>(begin: _position, end: newPosition);
|
||||||
);
|
const curve = Curves.easeOutCubic;
|
||||||
|
_positionAnimation = tween.animate(CurvedAnimation(parent: _positionAnimationController, curve: curve));
|
||||||
|
_positionAnimationController
|
||||||
|
..duration = _getAnimationDurationForVelocity(curve: curve, tween: tween, targetPixelPerSecond: pps)
|
||||||
|
..forward(from: 0.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (_scale != _startScale) {
|
||||||
|
_lastScaleGestureDate = DateTime.now();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Duration _getAnimationDurationForVelocity({
|
||||||
|
Cubic curve,
|
||||||
|
Tween<Offset> tween,
|
||||||
|
Offset targetPixelPerSecond,
|
||||||
|
}) {
|
||||||
|
assert(targetPixelPerSecond != Offset.zero);
|
||||||
|
// find initial animation velocity over the first 20% of the specified curve
|
||||||
|
const t = 0.2;
|
||||||
|
final animationVelocity = (tween.end - tween.begin).distance * curve.transform(t) / t;
|
||||||
|
final gestureVelocity = targetPixelPerSecond.distance;
|
||||||
|
return Duration(milliseconds: (animationVelocity / gestureVelocity * 1000).round());
|
||||||
}
|
}
|
||||||
|
|
||||||
void onTap(TapUpDetails details) {
|
void onTap(TapUpDetails details) {
|
||||||
|
|
Loading…
Reference in a new issue