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

Make FlingHandler use velocity as the activation metric. #2796

Merged
merged 30 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
aaa4c14
fling now detects movement by velocity, first working version tested …
latekvo Mar 8, 2024
618e1e3
fix commit requirement error
latekvo Mar 8, 2024
38e736a
- applied fling changes from web to native android devices
latekvo Mar 8, 2024
a8f0ed1
add missing function,
latekvo Mar 8, 2024
17d7b22
Update android/src/main/java/com/swmansion/gesturehandler/core/FlingG…
latekvo Mar 11, 2024
fba0a25
Applied changes from review.
latekvo Mar 11, 2024
53ec2f3
resolved merge conflict
latekvo Mar 11, 2024
0e94b6f
Applied review suggestions.
latekvo Mar 11, 2024
adb8546
Fix pointer not being detected out of bounds on web.
latekvo Mar 11, 2024
555862e
Simplify code.
latekvo Mar 11, 2024
6284b70
Merge branch 'main' into @latekvo/fix_fling
latekvo Mar 11, 2024
5344a7a
Apply review suggestions.
latekvo Mar 11, 2024
92e7191
Optimization
latekvo Mar 12, 2024
dacb57f
Merge branch '@latekvo/fix_fling' of https://github.com/software-mans…
latekvo Mar 12, 2024
dd34b38
Apply changes to kotlin code
latekvo Mar 12, 2024
13138be
Move code into a separate Vector class. Decrease sensitivity.
latekvo Mar 12, 2024
63007f4
Apply changes from JS to KT: separate logic into a separate class
latekvo Mar 12, 2024
bf8693a
Minor cleanup.
latekvo Mar 12, 2024
774bdb1
- moved Vector class to separate file
latekvo Mar 13, 2024
d3cb42c
Applied cosmetic review suggestions.
latekvo Mar 13, 2024
534050e
Fling and Vector code cleanup.
latekvo Mar 13, 2024
19fc039
Merge branch 'main' into @latekvo/fix_fling
latekvo Mar 14, 2024
b4711c6
- Velocity tracker now tracks the entire user movement.
latekvo Mar 14, 2024
ec8d55f
Merge branch '@latekvo/fix_fling' of https://github.com/software-mans…
latekvo Mar 14, 2024
93a238d
Create alternative static constructors for Vector, simplified code, r…
latekvo Mar 14, 2024
4e2c455
Merge branch 'main' into @latekvo/fix_fling
latekvo Mar 15, 2024
1af78a7
Remove potential edge-case crash
latekvo Mar 15, 2024
6f70fed
Merge branch 'main' into @latekvo/fix_fling
latekvo Mar 15, 2024
b84adae
Make Vector members private readonly, make magnitude a getter of priv…
latekvo Mar 15, 2024
06f38f3
Merge branch '@latekvo/fix_fling' of https://github.com/software-mans…
latekvo Mar 15, 2024
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
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,15 @@ import android.os.Handler
import android.os.Looper
import android.view.MotionEvent
import android.view.VelocityTracker
import kotlin.math.abs
import kotlin.math.hypot

class FlingGestureHandler : GestureHandler<FlingGestureHandler>() {
var numberOfPointersRequired = DEFAULT_NUMBER_OF_TOUCHES_REQUIRED
var direction = DEFAULT_DIRECTION

private val maxDurationMs = DEFAULT_MAX_DURATION_MS
private val minAcceptableDelta = DEFAULT_MIN_ACCEPTABLE_DELTA
private var startX = 0f
private var startY = 0f
private val minVelocity = DEFAULT_MIN_ACCEPTABLE_DELTA
m-bert marked this conversation as resolved.
Show resolved Hide resolved
private val minDirectionAlignment = DEFAULT_MIN_DIRECTION_ALIGNMENT
latekvo marked this conversation as resolved.
Show resolved Hide resolved
private var handler: Handler? = null
private var maxNumberOfPointersSimultaneously = 0
private val failDelayed = Runnable { fail() }
Expand All @@ -26,8 +24,6 @@ class FlingGestureHandler : GestureHandler<FlingGestureHandler>() {
}

private fun startFling(event: MotionEvent) {
startX = event.rawX
startY = event.rawY
begin()
maxNumberOfPointersSimultaneously = 1
if (handler == null) {
Expand All @@ -41,15 +37,10 @@ class FlingGestureHandler : GestureHandler<FlingGestureHandler>() {
private fun tryEndFling(event: MotionEvent): Boolean {
data class SimpleVector(val x: Double, val y: Double)

fun toSafeNumber(unsafe: Double): Double {
return if (unsafe.isFinite())
unsafe
else
0.0
}
fun toSafeNumber(unsafe: Double): Double = if (unsafe.isFinite()) unsafe else 0.0

fun toUnitVector(vec: SimpleVector): SimpleVector {
val magnitude = abs(vec.x + vec.y)
val magnitude = hypot(vec.x, vec.y)
// toSafeNumber protects against division by zero
return SimpleVector(
toSafeNumber(vec.x / magnitude),
Expand All @@ -67,12 +58,13 @@ class FlingGestureHandler : GestureHandler<FlingGestureHandler>() {
return unitA.x * unitB.x + unitA.y * unitB.y
}

val directionVector = SimpleVector(
(if (direction and DIRECTION_LEFT != 0) -1.0 else 0.0) +
(if (direction and DIRECTION_RIGHT != 0) 1.0 else 0.0),
(if (direction and DIRECTION_UP != 0) -1.0 else 0.0) +
(if (direction and DIRECTION_DOWN != 0) 1.0 else 0.0),
)
fun compareAlignment(
vec: SimpleVector,
directionVec: SimpleVector,
direction: Int
): Boolean =
compareSimilarity(vec, directionVec) > minDirectionAlignment &&
(this.direction and direction != 0)

val velocityTracker = VelocityTracker.obtain()
addVelocityMovement(velocityTracker, event)
Expand All @@ -83,13 +75,19 @@ class FlingGestureHandler : GestureHandler<FlingGestureHandler>() {
velocityTracker.yVelocity.toDouble()
)

val alignmentList = arrayOf(
compareAlignment(velocityVector, SimpleVector(-1.0, 0.0), DIRECTION_LEFT),
compareAlignment(velocityVector, SimpleVector(1.0, 0.0), DIRECTION_RIGHT),
compareAlignment(velocityVector, SimpleVector(0.0, -1.0), DIRECTION_UP),
compareAlignment(velocityVector, SimpleVector(0.0, 1.0), DIRECTION_DOWN)
)

velocityTracker.recycle()

// hypot may be overkill for this simple function, dot product may be sufficient
val totalVelocity = hypot(velocityVector.x, velocityVector.y)

val isAligned = compareSimilarity(directionVector, velocityVector) > 0.75
val isFast = totalVelocity > this.minAcceptableDelta
val isAligned = alignmentList.reduce { any, element -> any or element }
latekvo marked this conversation as resolved.
Show resolved Hide resolved
val isFast = totalVelocity > this.minVelocity

return if (
maxNumberOfPointersSimultaneously == numberOfPointersRequired &&
Expand Down Expand Up @@ -154,6 +152,7 @@ class FlingGestureHandler : GestureHandler<FlingGestureHandler>() {
companion object {
private const val DEFAULT_MAX_DURATION_MS: Long = 800
private const val DEFAULT_MIN_ACCEPTABLE_DELTA: Long = 2000
latekvo marked this conversation as resolved.
Show resolved Hide resolved
private const val DEFAULT_MIN_DIRECTION_ALIGNMENT: Double = 0.75
private const val DEFAULT_DIRECTION = DIRECTION_RIGHT
private const val DEFAULT_NUMBER_OF_TOUCHES_REQUIRED = 1
}
Expand Down
44 changes: 27 additions & 17 deletions src/web/handlers/FlingGestureHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,28 @@ import { AdaptedEvent, Config } from '../interfaces';
import GestureHandler from './GestureHandler';

const DEFAULT_MAX_DURATION_MS = 800;
const DEFAULT_MIN_ACCEPTABLE_DELTA = 400;
const DEFAULT_MIN_VELOCITY = 400;
const DEFAULT_MIN_DIRECTION_ALIGNMENT = 0.75;
const DEFAULT_DIRECTION = Direction.RIGHT;
const DEFAULT_NUMBER_OF_TOUCHES_REQUIRED = 1;

export default class FlingGestureHandler extends GestureHandler {
private numberOfPointersRequired = DEFAULT_NUMBER_OF_TOUCHES_REQUIRED;
private direction = DEFAULT_DIRECTION;
private shouldCancelWhenOutside = false;
latekvo marked this conversation as resolved.
Show resolved Hide resolved

private maxDurationMs = DEFAULT_MAX_DURATION_MS;
private minAcceptableDelta = DEFAULT_MIN_ACCEPTABLE_DELTA;
private minVelocity = DEFAULT_MIN_VELOCITY;
private minDirectionAlignment = DEFAULT_MIN_DIRECTION_ALIGNMENT;
private delayTimeout!: number;

private maxNumberOfPointersSimultaneously = 0;
private keyPointer = NaN;

public init(ref: number, propsRef: React.RefObject<unknown>): void {
super.init(ref, propsRef);

this.setShouldCancelWhenOutside(this.shouldCancelWhenOutside);
}

public updateGestureConfig({ enabled = true, ...props }: Config): void {
Expand Down Expand Up @@ -52,7 +57,7 @@ export default class FlingGestureHandler extends GestureHandler {
};

const toUnitVector = (vec: SimpleVector): SimpleVector => {
const magnitude = Math.abs(vec.x + vec.y);
const magnitude = Math.hypot(vec.x, vec.y);
// division by 0 may occur here
return {
x: toSafeNumber(vec.x / magnitude),
Expand All @@ -70,29 +75,34 @@ export default class FlingGestureHandler extends GestureHandler {
return unitA.x * unitB.x + unitA.y * unitB.y;
};

const directionVector: SimpleVector = {
x:
(this.direction & Direction.LEFT ? -1 : 0) +
(this.direction & Direction.RIGHT ? 1 : 0),
y:
(this.direction & Direction.UP ? -1 : 0) +
(this.direction & Direction.DOWN ? 1 : 0),
const compareAlignment = (
vec: SimpleVector,
directionVec: SimpleVector,
direction: number
): boolean => {
return !!(
compareSimilarity(vec, directionVec) > this.minDirectionAlignment &&
this.direction & direction
);
};

const velocityVector: SimpleVector = {
x: this.tracker.getVelocityX(this.keyPointer),
y: this.tracker.getVelocityY(this.keyPointer),
};

// hypot may be overkill for this simple function, simple addition would be sufficient
// list of alignments to all activated directions
const alignmentList = [
compareAlignment(velocityVector, { x: -1, y: 0 }, Direction.LEFT),
compareAlignment(velocityVector, { x: 1, y: 0 }, Direction.RIGHT),
compareAlignment(velocityVector, { x: 0, y: -1 }, Direction.UP),
compareAlignment(velocityVector, { x: 0, y: 1 }, Direction.DOWN),
];

const totalVelocity = Math.hypot(velocityVector.x, velocityVector.y);

const movementAlignment = compareSimilarity(
directionVector,
velocityVector
);
const isAligned = movementAlignment > 0.75;
const isFast = totalVelocity > this.minAcceptableDelta;
const isAligned = alignmentList.reduce((any, element) => any || element);
latekvo marked this conversation as resolved.
Show resolved Hide resolved
const isFast = totalVelocity > this.minVelocity;

if (
this.maxNumberOfPointersSimultaneously ===
Expand Down