Skip to content

Commit

Permalink
Improve the fling logic
Browse files Browse the repository at this point in the history
  • Loading branch information
mattleibow committed Aug 11, 2019
1 parent 644931a commit 3aa4105
Show file tree
Hide file tree
Showing 4 changed files with 38 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -31,17 +31,28 @@ public GestureSurfacePage()

BindingContext = this;

//gestureSurface.FlingDetected += (sender, e) =>
//{
// gestureSurface.InvalidateSurface();
//};
gestureSurface.FlingDetected += (sender, e) =>
{
var easing = Easing.SinOut;
var ratio = e.VelocityX / e.VelocityY;
gestureSurface.AbortAnimation("Fling");
var animation = new Animation(v => Transform(new SKPoint((float)(v * ratio), (float)v), SKPoint.Empty, 1, 0), e.VelocityY * 0.01f, 0, easing);
animation.Commit(gestureSurface, "Fling", 16, 1000);
};

gestureSurface.TransformDetected += (sender, e) =>
{
gestureSurface.AbortAnimation("Fling");
Transform(e.Center, e.PreviousCenter, e.ScaleDelta, e.RotationDelta);
};

gestureSurface.DoubleTapDetected += (sender, e) =>
{
gestureSurface.AbortAnimation("Fling");
Transform(e.Location, e.Location, 1.5f, 0);
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;

namespace SkiaSharp.Extended.Controls
{
public partial class SKGestureSurfaceView
{
private class FlingTracker
{
private const long maxTicks = 200 * 10000; // Use only events from the last 200 ms
private const long thresholdTicks = 200 * TimeSpan.TicksPerMillisecond; // Use only events from the last 200 ms

private const int maxSize = 2;

Expand Down Expand Up @@ -50,12 +51,13 @@ public SKPoint CalculateVelocity(long id, long now)
var nowItem = array[1];

// use last 2 events
if (now - lastItem.Time < maxTicks)
if (now - lastItem.TimeTicks < thresholdTicks)
{
velocityX = (nowItem.X - lastItem.X) * 10000000 / (nowItem.Time - lastItem.Time);
velocityY = (nowItem.Y - lastItem.Y) * 10000000 / (nowItem.Time - lastItem.Time);
velocityX = (nowItem.X - lastItem.X) * TimeSpan.TicksPerSecond / (nowItem.TimeTicks - lastItem.TimeTicks);
velocityY = (nowItem.Y - lastItem.Y) * TimeSpan.TicksPerSecond / (nowItem.TimeTicks - lastItem.TimeTicks);
}

// return the velocity in pixels per second
return new SKPoint(velocityX, velocityY);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@ public partial class SKGestureSurfaceView
{
private struct FlingTrackerEvent
{
public FlingTrackerEvent(float x, float y, long time)
public FlingTrackerEvent(float x, float y, long timeTicks)
{
X = x;
Y = y;
Time = time;
TimeTicks = timeTicks;
}

public float X { get; }

public float Y { get; }

public long Time { get; }
public long TimeTicks { get; }
}
}
}
25 changes: 13 additions & 12 deletions SkiaSharp.Extended.Controls/source/SKGestureSurfaceView.cs
Original file line number Diff line number Diff line change
@@ -1,23 +1,24 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using SkiaSharp.Views.Forms;

namespace SkiaSharp.Extended.Controls
{
public partial class SKGestureSurfaceView : SKDynamicSurfaceView
{
private const int shortTap = 125;
private const int shortClick = 250;
private const int delayTap = 200;
private const int longTap = 500;
private const int touchSlop = 8;
private const int flingVelocity = 200;
private const long shortTapTicks = 125 * TimeSpan.TicksPerMillisecond;
private const long shortClickTicks = 250 * TimeSpan.TicksPerMillisecond;
private const int delayTapMilliseconds = 200;
private const long longTapTicks = 500 * TimeSpan.TicksPerMillisecond;
private const int touchSlopPixels = 8;
private const int flingVelocityThreshold = 200; // pixels per second

private readonly Dictionary<long, TouchEvent> touches = new Dictionary<long, TouchEvent>();
private readonly FlingTracker flingTracker = new FlingTracker();
private SKPoint initialTouch = SKPoint.Empty;
private System.Threading.Timer multiTapTimer;
private Timer multiTapTimer;
private int tapCount = 0;
private TouchMode touchMode = TouchMode.None;
private PinchValue previousValues;
Expand Down Expand Up @@ -226,16 +227,16 @@ private bool OnTouchReleased(SKTouchEventArgs e)
{
// check to see if it was a fling
var velocity = flingTracker.CalculateVelocity(e.Id, ticks);
if (Math.Abs(velocity.X) > flingVelocity || Math.Abs(velocity.Y) > flingVelocity)
if (Math.Abs(velocity.X * velocity.Y) > (flingVelocityThreshold * flingVelocityThreshold))
{
var args = new SKFlingDetectedEventArgs(velocity.X, velocity.Y);
OnFlingDetected(args);
handled = args.Handled;
}

// when tapping, the finger never goes to exactly the same location
var isAround = SKPoint.Distance(releasedTouch.Location, initialTouch) < touchSlop;
if (isAround && (ticks - releasedTouch.Tick) < (e.DeviceType == SKTouchDeviceType.Mouse ? shortClick : longTap) * 10000)
var isAround = SKPoint.Distance(releasedTouch.Location, initialTouch) < touchSlopPixels;
if (isAround && (ticks - releasedTouch.Tick) < (e.DeviceType == SKTouchDeviceType.Mouse ? shortClickTicks : longTapTicks))
{
// add a timer to detect the type of tap (single or multi)
void TimerHandler(object state)
Expand All @@ -260,9 +261,9 @@ void TimerHandler(object state)
multiTapTimer?.Dispose();
multiTapTimer = null;
};
multiTapTimer = new System.Threading.Timer(TimerHandler, location, delayTap, -1);
multiTapTimer = new Timer(TimerHandler, location, delayTapMilliseconds, -1);
}
else if (isAround && (ticks - releasedTouch.Tick) >= longTap * 10000)
else if (isAround && (ticks - releasedTouch.Tick) >= longTapTicks)
{
// if the finger was down for a long time, then it is a long tap
if (!handled)
Expand Down

0 comments on commit 3aa4105

Please sign in to comment.