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

Fix onBegin and onTouchesDown events ordering on Android #2283

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -57,6 +57,7 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
var lastAbsolutePositionY = 0f
private set

private var isInitialized = false
private var manualActivation = false

private var lastEventOffsetX = 0f
Expand All @@ -68,6 +69,10 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
private var onTouchEventListener: OnTouchEventListener? = null
private var interactionController: GestureHandlerInteractionController? = null

// a field to store a motion event that caused the touch event to be dispatched, in case the state
// change is triggered in the callback, this event will be used to initialize the gesture
private var eventTriggeringStateChangeInTouchEventHandler: MotionEvent? = null

@Suppress("UNCHECKED_CAST")
protected fun self(): ConcreteGestureHandlerT = this as ConcreteGestureHandlerT

Expand All @@ -88,8 +93,8 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
onTouchEventListener?.onHandlerUpdate(self(), event)
}

open fun dispatchTouchEvent() {
if (changedTouchesPayload != null) {
open fun tryDispatchingTouchEvent() {
if (needsPointerData && changedTouchesPayload != null) {
onTouchEventListener?.onTouchEvent(self())
}
}
Expand Down Expand Up @@ -380,7 +385,7 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
}
}

private fun dispatchTouchDownEvent(event: MotionEvent) {
private fun handleTouchDownEvent(event: MotionEvent) {
changedTouchesPayload = null
touchEventType = RNGestureHandlerTouchEvent.EVENT_TOUCH_DOWN
val pointerId = event.getPointerId(event.actionIndex)
Expand All @@ -398,10 +403,10 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
addChangedPointer(trackedPointers[pointerId]!!)
extractAllPointersData()

dispatchTouchEvent()
tryDispatchingTouchEvent()
}

private fun dispatchTouchUpEvent(event: MotionEvent) {
private fun handleTouchUpEvent(event: MotionEvent) {
extractAllPointersData()
changedTouchesPayload = null
touchEventType = RNGestureHandlerTouchEvent.EVENT_TOUCH_UP
Expand All @@ -420,10 +425,10 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
trackedPointers[pointerId] = null
trackedPointersCount--

dispatchTouchEvent()
tryDispatchingTouchEvent()
}

private fun dispatchTouchMoveEvent(event: MotionEvent) {
private fun handleTouchMoveEvent(event: MotionEvent) {
changedTouchesPayload = null
touchEventType = RNGestureHandlerTouchEvent.EVENT_TOUCH_MOVE
val offsetX = event.rawX - event.x
Expand All @@ -450,20 +455,28 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
// only info about one pointer)
if (pointersAdded > 0) {
extractAllPointersData()
dispatchTouchEvent()
tryDispatchingTouchEvent()
}
}

fun updatePointerData(event: MotionEvent) {
if (event.actionMasked == MotionEvent.ACTION_DOWN || event.actionMasked == MotionEvent.ACTION_POINTER_DOWN) {
dispatchTouchDownEvent(event)
dispatchTouchMoveEvent(event)
} else if (event.actionMasked == MotionEvent.ACTION_UP || event.actionMasked == MotionEvent.ACTION_POINTER_UP) {
dispatchTouchMoveEvent(event)
dispatchTouchUpEvent(event)
} else if (event.actionMasked == MotionEvent.ACTION_MOVE) {
dispatchTouchMoveEvent(event)
fun updatePointerData(event: MotionEvent, sourceEvent: MotionEvent) {
eventTriggeringStateChangeInTouchEventHandler = sourceEvent

when (event.actionMasked) {
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_POINTER_DOWN -> {
handleTouchDownEvent(event)
handleTouchMoveEvent(event)
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_POINTER_UP -> {
handleTouchMoveEvent(event)
handleTouchUpEvent(event)
}
MotionEvent.ACTION_MOVE -> {
handleTouchMoveEvent(event)
}
}

eventTriggeringStateChangeInTouchEventHandler = null
}

private fun extractAllPointersData() {
Expand All @@ -490,7 +503,7 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
trackedPointersCount = 0
trackedPointers.fill(null)

dispatchTouchEvent()
tryDispatchingTouchEvent()
}

private fun addChangedPointer(pointerData: PointerData) {
Expand Down Expand Up @@ -667,6 +680,17 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
}
}

fun initialize(withEvent: MotionEvent? = null) {
if (!isInitialized) {
val event = withEvent ?: eventTriggeringStateChangeInTouchEventHandler ?: throw IllegalStateException("No event to initialize handler")
onInitialize(event)
}

isInitialized = true
}

open fun onInitialize(event: MotionEvent) {}

// responsible for resetting the state of handler upon activation (may be called more than once
// if the handler is waiting for failure of other one)
open fun resetProgress() {}
Expand Down Expand Up @@ -703,6 +727,7 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
trackedPointersCount = 0
trackedPointers.fill(null)
touchEventType = RNGestureHandlerTouchEvent.EVENT_UNDETERMINED
isInitialized = false
onReset()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -259,20 +259,9 @@ class GestureHandlerOrchestrator(
val action = sourceEvent.actionMasked
val event = transformEventToViewCoords(handler.view, MotionEvent.obtain(sourceEvent))

// Touch events are sent before the handler itself has a chance to process them,
// mainly because `onTouchesUp` shoul be send befor gesture finishes. This means that
// the first `onTouchesDown` event is sent before a gesture begins, activation in
// callback for this event causes problems because the handler doesn't have a chance
// to initialize itself with starting values of pointer (in pan this causes translation
// to be equal to the coordinates of the pointer). The simplest solution is to send
// the first `onTouchesDown` event after the handler processes it and changes state
// to `BEGAN`.
if (handler.needsPointerData && handler.state != 0) {
handler.updatePointerData(event)
}
handler.updatePointerData(event, sourceEvent)

if (!handler.isAwaiting || action != MotionEvent.ACTION_MOVE) {
val isFirstEvent = handler.state == 0
handler.handle(event, sourceEvent)
if (handler.isActive) {
// After handler is done waiting for other one to fail its progress should be
Expand All @@ -289,10 +278,6 @@ class GestureHandlerOrchestrator(
handler.dispatchHandlerUpdate(event)
}

if (handler.needsPointerData && isFirstEvent) {
handler.updatePointerData(event)
}

// if event was of type UP or POINTER_UP we request handler to stop tracking now that
// the event has been dispatched
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_POINTER_UP) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,8 @@ class LongPressGestureHandler(context: Context) : GestureHandler<LongPressGestur

override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) {
if (state == STATE_UNDETERMINED) {
previousTime = SystemClock.uptimeMillis()
startTime = previousTime
initialize(sourceEvent)
begin()
startX = sourceEvent.rawX
startY = sourceEvent.rawY
handler = Handler(Looper.getMainLooper())
if (minDurationMs > 0) {
handler!!.postDelayed({ activate() }, minDurationMs)
Expand Down Expand Up @@ -83,6 +80,13 @@ class LongPressGestureHandler(context: Context) : GestureHandler<LongPressGestur
}
}

override fun onInitialize(event: MotionEvent) {
previousTime = SystemClock.uptimeMillis()
startTime = previousTime
startX = event.rawX
startY = event.rawY
}

override fun dispatchStateChange(newState: Int, prevState: Int) {
previousTime = SystemClock.uptimeMillis()
super.dispatchStateChange(newState, prevState)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,12 +225,7 @@ class PanGestureHandler(context: Context?) : GestureHandler<PanGestureHandler>()
lastY = getLastPointerY(sourceEvent, averageTouches)
}
if (state == STATE_UNDETERMINED && sourceEvent.pointerCount >= minPointers) {
resetProgress()
offsetX = 0f
offsetY = 0f
velocityX = 0f
velocityY = 0f
velocityTracker = VelocityTracker.obtain()
initialize(sourceEvent)
addVelocityMovement(velocityTracker, sourceEvent)
begin()

Expand Down Expand Up @@ -285,6 +280,15 @@ class PanGestureHandler(context: Context?) : GestureHandler<PanGestureHandler>()
handler?.removeCallbacksAndMessages(null)
}

override fun onInitialize(event: MotionEvent) {
resetProgress()
offsetX = 0f
offsetY = 0f
velocityX = 0f
velocityY = 0f
velocityTracker = VelocityTracker.obtain()
}

override fun onReset() {
handler?.removeCallbacksAndMessages(null)
velocityTracker?.let {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,16 +52,7 @@ class PinchGestureHandler : GestureHandler<PinchGestureHandler>() {

override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) {
if (state == STATE_UNDETERMINED) {
val context = view!!.context
resetProgress()
scaleGestureDetector = ScaleGestureDetector(context, gestureListener)
val configuration = ViewConfiguration.get(context)
spanSlop = configuration.scaledTouchSlop.toFloat()

// set the focal point to the position of the first pointer as NaN causes the event not to arrive
this.focalPointX = event.x
this.focalPointY = event.y

initialize(sourceEvent)
begin()
}
scaleGestureDetector?.onTouchEvent(sourceEvent)
Expand Down Expand Up @@ -96,6 +87,18 @@ class PinchGestureHandler : GestureHandler<PinchGestureHandler>() {
resetProgress()
}

override fun onInitialize(event: MotionEvent) {
resetProgress()
val context = view!!.context
val configuration = ViewConfiguration.get(context)
scaleGestureDetector = ScaleGestureDetector(context, gestureListener)
spanSlop = configuration.scaledTouchSlop.toFloat()

// set the focal point to the position of the first pointer as NaN causes the event not to arrive
this.focalPointX = event.x
this.focalPointY = event.y
}

override fun resetProgress() {
velocity = 0.0
scale = 1.0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,7 @@ class RotationGestureHandler : GestureHandler<RotationGestureHandler>() {

override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) {
if (state == STATE_UNDETERMINED) {
resetProgress()
rotationGestureDetector = RotationGestureDetector(gestureListener)

// set the anchor to the position of the first pointer as NaN causes the event not to arrive
this.anchorX = event.x
this.anchorY = event.y

initialize(sourceEvent)
begin()
}
rotationGestureDetector?.onTouchEvent(sourceEvent)
Expand All @@ -75,6 +69,15 @@ class RotationGestureHandler : GestureHandler<RotationGestureHandler>() {
super.activate(force)
}

override fun onInitialize(event: MotionEvent) {
resetProgress()
rotationGestureDetector = RotationGestureDetector(gestureListener)

// set the anchor to the position of the first pointer as NaN causes the event not to arrive
this.anchorX = event.x
this.anchorY = event.y
}

override fun onReset() {
rotationGestureDetector = null
anchorX = Float.NaN
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,10 +108,7 @@ class TapGestureHandler : GestureHandler<TapGestureHandler>() {
val state = state
val action = sourceEvent.actionMasked
if (state == STATE_UNDETERMINED) {
offsetX = 0f
offsetY = 0f
startX = getLastPointerX(sourceEvent, true)
startY = getLastPointerY(sourceEvent, true)
initialize(sourceEvent)
}
if (action == MotionEvent.ACTION_POINTER_UP || action == MotionEvent.ACTION_POINTER_DOWN) {
offsetX += lastX - startX
Expand Down Expand Up @@ -148,6 +145,13 @@ class TapGestureHandler : GestureHandler<TapGestureHandler>() {
end()
}

override fun onInitialize(event: MotionEvent) {
offsetX = 0f
offsetY = 0f
startX = getLastPointerX(event, true)
startY = getLastPointerY(event, true)
}

override fun onCancel() {
handler?.removeCallbacksAndMessages(null)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -431,9 +431,15 @@ class RNGestureHandlerModule(reactContext: ReactApplicationContext?) :

override fun setGestureHandlerState(handlerTag: Int, newState: Int) {
registry.getHandler(handlerTag)?.let { handler ->
// Try to initialize the handler in case it was not yet initialized
handler.initialize()
// Try to transition to the BEGIN state (possible only from UNDETERMINED)
// to force the correct event flow
handler.begin()

when (newState) {
GestureHandler.STATE_ACTIVE -> handler.activate(force = true)
GestureHandler.STATE_BEGAN -> handler.begin()
GestureHandler.STATE_BEGAN -> {}
GestureHandler.STATE_END -> handler.end()
GestureHandler.STATE_FAILED -> handler.fail()
GestureHandler.STATE_CANCELLED -> handler.cancel()
Expand Down
Loading