Skip to content

Commit 5730bf9

Browse files
Allow Android Rotation Gesture to continue after second Pointer Lift and Land (#3057)
## Description On iOS Rotation Gesture is not getting finished/cancelled when one of two Pointers lifts up, allowing the Gesture to continue when a new second Pointer arrives On Android this behaviour differs – Rotation Gesture finishes whenever one of two Pointers lifts up, requiring to land 2 completely new Pointers in order to utilise Rotation Gesture again I understand that this may be closer to default Android Gesture Recogniser Behaviour, but find it unexpected and inconvenient Thus in this PR I’m adding Ability to tell Rotation Gesture Builder to enable iOS-like Behaviour, which allows Gesture Pause/Remain when second Pointer lifts/lands Additionally, I added some local toggleable Logs, which I find useful for Debugging, but I’m not sure if it aligns with this Repo Style Guide (or could noticeably reduce Performance) – please notify me, if it’s prohibited I think this Change may be usable for Gesture Handler Users, thus trying to push it to the Gesture Handler (not only my own Fork) and looking forward to any Feedback regarding both Code – Implementation/Naming and/or local Style Guide – and Docs, which I might’ve misaligned with Attached Gifs display what I mean (first one shows current Behaviour, second one – with new Modifier) <img src="https://github.com/user-attachments/assets/ef87aa99-db12-42a5-a5ef-1fed4b11f8ac" width="260"/> <img src="https://github.com/user-attachments/assets/a8337925-e1e9-4862-a5ed-b3b5cd5dde31" width="260"/> ## Test plan 1. Use `RotationGesture` without new Modifier (`secondPointerLiftFinishesGesture(value: boolean)`) and see if it works as expected (as before – Gesture finishes when one of two key Pointers is lifted) 2. Use `RotationGesture` with new Modifier (`secondPointerLiftFinishesGesture(value: boolean)`) and see if it works as expected (Gesture doesn't finish when second Pointer is lifted and continues when second Pointer is landed again)
1 parent a6741a9 commit 5730bf9

File tree

1 file changed

+53
-21
lines changed

1 file changed

+53
-21
lines changed

android/src/main/java/com/swmansion/gesturehandler/core/RotationGestureDetector.kt

Lines changed: 53 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ class RotationGestureDetector(private val gestureListener: OnRotationGestureList
5757
currentTime = event.eventTime
5858
val firstPointerIndex = event.findPointerIndex(pointerIds[0])
5959
val secondPointerIndex = event.findPointerIndex(pointerIds[1])
60+
61+
if (
62+
firstPointerIndex == MotionEvent.INVALID_POINTER_ID ||
63+
secondPointerIndex == MotionEvent.INVALID_POINTER_ID
64+
) {
65+
return
66+
}
67+
6068
val firstPtX = event.getX(firstPointerIndex)
6169
val firstPtY = event.getY(firstPointerIndex)
6270
val secondPtX = event.getX(secondPointerIndex)
@@ -68,6 +76,9 @@ class RotationGestureDetector(private val gestureListener: OnRotationGestureList
6876

6977
// Angle diff should be positive when rotating in clockwise direction
7078
val angle = -atan2(vectorY.toDouble(), vectorX.toDouble())
79+
80+
tryUnpause(angle)
81+
7182
rotation = if (previousAngle.isNaN()) {
7283
0.0
7384
} else previousAngle - angle
@@ -85,8 +96,29 @@ class RotationGestureDetector(private val gestureListener: OnRotationGestureList
8596
}
8697
}
8798

99+
/**
100+
* Gesture pauses when second Pointer lifts
101+
*
102+
* Then Detector waits for a new second Pointer to arrive to continue Handling
103+
* (last Pointer Lifting finishes the Gesture)
104+
*
105+
* @see tryPause
106+
* @see tryUnpause
107+
*/
108+
private var isPaused = false
109+
private fun tryPause() {
110+
if (isPaused) return
111+
isPaused = true
112+
}
113+
private fun tryUnpause(eventAngle: Double) {
114+
if (!isPaused) return
115+
previousAngle = eventAngle
116+
isPaused = false
117+
}
118+
88119
private fun finish() {
89120
if (isInProgress) {
121+
isPaused = false
90122
isInProgress = false
91123
gestureListener?.onRotationEnd(this)
92124
}
@@ -99,33 +131,33 @@ class RotationGestureDetector(private val gestureListener: OnRotationGestureList
99131
pointerIds[0] = event.getPointerId(event.actionIndex)
100132
pointerIds[1] = MotionEvent.INVALID_POINTER_ID
101133
}
102-
MotionEvent.ACTION_POINTER_DOWN -> if (!isInProgress) {
103-
pointerIds[1] = event.getPointerId(event.actionIndex)
104-
isInProgress = true
105-
previousTime = event.eventTime
106-
previousAngle = Double.NaN
107-
updateCurrent(event)
108-
gestureListener?.onRotationBegin(this)
134+
MotionEvent.ACTION_POINTER_DOWN -> {
135+
if (!isInProgress || isPaused) {
136+
pointerIds[1] = event.getPointerId(event.actionIndex)
137+
updateCurrent(event)
138+
}
139+
if (!isInProgress) {
140+
isInProgress = true
141+
previousTime = event.eventTime
142+
previousAngle = Double.NaN
143+
gestureListener?.onRotationBegin(this)
144+
}
109145
}
110146
MotionEvent.ACTION_MOVE -> if (isInProgress) {
111147
updateCurrent(event)
112-
gestureListener?.onRotation(this)
148+
if (!isPaused) {
149+
gestureListener?.onRotation(this)
150+
}
113151
}
114-
MotionEvent.ACTION_POINTER_UP -> {
152+
MotionEvent.ACTION_POINTER_UP -> if (isInProgress) {
115153
val pointerId = event.getPointerId(event.actionIndex)
116-
117-
// All key pointers are up
118-
if (!isInProgress && pointerId == pointerIds[0]) {
119-
gestureListener?.onRotationEnd(this)
120-
}
121-
122-
// One of the key pointers is up
123-
if (isInProgress && pointerIds.contains(pointerId)) {
124-
if (pointerId == pointerIds[0]) {
125-
pointerIds[0] = pointerIds[1]
126-
}
154+
if (pointerId == pointerIds[0]) {
155+
pointerIds[0] = pointerIds[1]
156+
pointerIds[1] = MotionEvent.INVALID_POINTER_ID
157+
tryPause()
158+
} else if (pointerId == pointerIds[1]) {
127159
pointerIds[1] = MotionEvent.INVALID_POINTER_ID
128-
isInProgress = false
160+
tryPause()
129161
}
130162
}
131163
MotionEvent.ACTION_UP -> finish()

0 commit comments

Comments
 (0)