Skip to content

Commit 5271d96

Browse files
authored
Make base GestureHandler on Android non generic (#3491)
## Description Depends on #3489 On Android, `GestureHandler` was a generic class specialized by the concrete type of the handler. The only place it was used was `applySelf` method, which allowed for inherited methods to return the concrete handler: ``` tapHandler.setEnabled(true) // returns TapGestureHandler instead of GestureHandler ``` ...but we don't use this pattern anywhere. It made much more sense in Java, than it does in Kotlin, where we can simply use `apply`, `let`, etc. when there's a need to do multiple operations on the same object. Now, it simply makes the logic dealing with gesture handlers more complicated as it also needs to keep track of the generic type, which in most cases is `*` so it doesn't give us more type safety. This PR makes it so that the base `GestureHandler` class is no longer generic, which should make life easier when dealing with non-concrete gesture handlers in the future. ## Test plan Build example apps
1 parent 3afdaa7 commit 5271d96

24 files changed

Lines changed: 112 additions & 140 deletions

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/FlingGestureHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import android.view.VelocityTracker
88
import com.facebook.react.bridge.ReadableMap
99
import com.swmansion.gesturehandler.react.eventbuilders.FlingGestureHandlerEventDataBuilder
1010

11-
class FlingGestureHandler : GestureHandler<FlingGestureHandler>() {
11+
class FlingGestureHandler : GestureHandler() {
1212
var numberOfPointersRequired = DEFAULT_NUMBER_OF_TOUCHES_REQUIRED
1313
var direction = DEFAULT_DIRECTION
1414

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandler.kt

Lines changed: 17 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import com.swmansion.gesturehandler.react.eventbuilders.GestureHandlerEventDataB
2323
import java.lang.IllegalStateException
2424
import java.util.*
2525

26-
open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestureHandlerT>> {
26+
open class GestureHandler {
2727
private val trackedPointerIDs = IntArray(MAX_POINTERS_COUNT)
2828
private var trackedPointersIDsCount = 0
2929
private val windowOffset = IntArray(2) { 0 }
@@ -79,37 +79,30 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
7979
protected set
8080
protected var shouldCancelWhenOutside = false
8181
protected var orchestrator: GestureHandlerOrchestrator? = null
82-
private var onTouchEventListener: OnTouchEventListener? = null
82+
var onTouchEventListener: OnTouchEventListener? = null
8383
private var interactionController: GestureHandlerInteractionController? = null
8484
var pointerType: Int = POINTER_TYPE_OTHER
8585
private set
8686

8787
protected var mouseButton = 0
8888

89-
@Suppress("UNCHECKED_CAST")
90-
protected fun self(): ConcreteGestureHandlerT = this as ConcreteGestureHandlerT
91-
92-
protected inline fun applySelf(block: ConcreteGestureHandlerT.() -> Unit): ConcreteGestureHandlerT = self().apply {
93-
block()
94-
}
95-
9689
// properties set and accessed only by the orchestrator
9790
var activationIndex = 0
9891
var isActive = false
9992
var isAwaiting = false
10093
var shouldResetProgress = false
10194

10295
open fun dispatchStateChange(newState: Int, prevState: Int) {
103-
onTouchEventListener?.onStateChange(self(), newState, prevState)
96+
onTouchEventListener?.onStateChange(this, newState, prevState)
10497
}
10598

10699
open fun dispatchHandlerUpdate(event: MotionEvent) {
107-
onTouchEventListener?.onHandlerUpdate(self(), event)
100+
onTouchEventListener?.onHandlerUpdate(this, event)
108101
}
109102

110103
open fun dispatchTouchEvent() {
111104
if (changedTouchesPayload != null) {
112-
onTouchEventListener?.onTouchEvent(self())
105+
onTouchEventListener?.onTouchEvent(this)
113106
}
114107
}
115108

@@ -122,7 +115,7 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
122115
mouseButton = DEFAULT_MOUSE_BUTTON
123116
}
124117

125-
fun hasCommonPointers(other: GestureHandler<*>): Boolean {
118+
fun hasCommonPointers(other: GestureHandler): Boolean {
126119
for (i in trackedPointerIDs.indices) {
127120
if (trackedPointerIDs[i] != -1 && other.trackedPointerIDs[i] != -1) {
128121
return true
@@ -131,14 +124,7 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
131124
return false
132125
}
133126

134-
fun setHitSlop(
135-
leftPad: Float,
136-
topPad: Float,
137-
rightPad: Float,
138-
bottomPad: Float,
139-
width: Float,
140-
height: Float,
141-
): ConcreteGestureHandlerT = applySelf {
127+
fun setHitSlop(leftPad: Float, topPad: Float, rightPad: Float, bottomPad: Float, width: Float, height: Float) {
142128
if (hitSlop == null) {
143129
hitSlop = FloatArray(6)
144130
}
@@ -162,10 +148,9 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
162148
}
163149
}
164150

165-
fun setHitSlop(padding: Float): ConcreteGestureHandlerT =
166-
setHitSlop(padding, padding, padding, padding, HIT_SLOP_NONE, HIT_SLOP_NONE)
151+
fun setHitSlop(padding: Float) = setHitSlop(padding, padding, padding, padding, HIT_SLOP_NONE, HIT_SLOP_NONE)
167152

168-
fun setInteractionController(controller: GestureHandlerInteractionController?): ConcreteGestureHandlerT = applySelf {
153+
fun setInteractionController(controller: GestureHandlerInteractionController?) {
169154
interactionController = controller
170155
}
171156

@@ -331,7 +316,7 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
331316
}
332317

333318
// exception to help debug https://github.com/software-mansion/react-native-gesture-handler/issues/1188
334-
class AdaptEventException(handler: GestureHandler<*>, event: MotionEvent, e: IllegalArgumentException) :
319+
class AdaptEventException(handler: GestureHandler, event: MotionEvent, e: IllegalArgumentException) :
335320
Exception(
336321
"""
337322
handler: ${handler::class.simpleName}
@@ -594,31 +579,31 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
594579
state != STATE_END &&
595580
trackedPointersIDsCount > 0
596581

597-
open fun shouldRequireToWaitForFailure(handler: GestureHandler<*>): Boolean {
582+
open fun shouldRequireToWaitForFailure(handler: GestureHandler): Boolean {
598583
if (handler === this) {
599584
return false
600585
}
601586

602587
return interactionController?.shouldRequireHandlerToWaitForFailure(this, handler) ?: false
603588
}
604589

605-
fun shouldWaitForHandlerFailure(handler: GestureHandler<*>): Boolean {
590+
fun shouldWaitForHandlerFailure(handler: GestureHandler): Boolean {
606591
if (handler === this) {
607592
return false
608593
}
609594

610595
return interactionController?.shouldWaitForHandlerFailure(this, handler) ?: false
611596
}
612597

613-
open fun shouldRecognizeSimultaneously(handler: GestureHandler<*>): Boolean {
598+
open fun shouldRecognizeSimultaneously(handler: GestureHandler): Boolean {
614599
if (handler === this) {
615600
return true
616601
}
617602

618603
return interactionController?.shouldRecognizeSimultaneously(this, handler) ?: false
619604
}
620605

621-
open fun shouldBeCancelledBy(handler: GestureHandler<*>): Boolean {
606+
open fun shouldBeCancelledBy(handler: GestureHandler): Boolean {
622607
if (handler === this) {
623608
return false
624609
}
@@ -714,7 +699,7 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
714699
* Returns true if the view this handler is attached to is a descendant of the view the other handler
715700
* is attached to and false otherwise.
716701
*/
717-
fun isDescendantOf(of: GestureHandler<*>): Boolean {
702+
fun isDescendantOf(of: GestureHandler): Boolean {
718703
var view = this.view?.parent as? View
719704
while (view != null) {
720705
if (view == of.view) {
@@ -831,11 +816,6 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
831816
}
832817
}
833818

834-
fun setOnTouchEventListener(listener: OnTouchEventListener?): GestureHandler<*> {
835-
onTouchEventListener = listener
836-
return this
837-
}
838-
839819
override fun toString(): String {
840820
val viewString = if (view == null) null else view!!.javaClass.simpleName
841821
return this.javaClass.simpleName + "@[" + tag + "]:" + viewString
@@ -851,7 +831,7 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
851831
val lastPositionInWindowY: Float
852832
get() = lastAbsolutePositionY + lastEventOffsetY - windowOffset[1]
853833

854-
abstract class Factory<T : GestureHandler<T>> {
834+
abstract class Factory<T : GestureHandler> {
855835
abstract val type: Class<T>
856836
abstract val name: String
857837

@@ -899,7 +879,7 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
899879
private const val KEY_HIT_SLOP_WIDTH = "width"
900880
private const val KEY_HIT_SLOP_HEIGHT = "height"
901881

902-
private fun handleHitSlopProperty(handler: GestureHandler<*>, config: ReadableMap) {
882+
private fun handleHitSlopProperty(handler: GestureHandler, config: ReadableMap) {
903883
if (config.getType(KEY_HIT_SLOP) == ReadableType.Number) {
904884
val hitSlop = PixelUtil.toPixelFromDIP(config.getDouble(KEY_HIT_SLOP))
905885
handler.setHitSlop(
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
package com.swmansion.gesturehandler.core
22

33
interface GestureHandlerInteractionController {
4-
fun shouldWaitForHandlerFailure(handler: GestureHandler<*>, otherHandler: GestureHandler<*>): Boolean
5-
fun shouldRequireHandlerToWaitForFailure(handler: GestureHandler<*>, otherHandler: GestureHandler<*>): Boolean
6-
fun shouldRecognizeSimultaneously(handler: GestureHandler<*>, otherHandler: GestureHandler<*>): Boolean
7-
fun shouldHandlerBeCancelledBy(handler: GestureHandler<*>, otherHandler: GestureHandler<*>): Boolean
4+
fun shouldWaitForHandlerFailure(handler: GestureHandler, otherHandler: GestureHandler): Boolean
5+
fun shouldRequireHandlerToWaitForFailure(handler: GestureHandler, otherHandler: GestureHandler): Boolean
6+
fun shouldRecognizeSimultaneously(handler: GestureHandler, otherHandler: GestureHandler): Boolean
7+
fun shouldHandlerBeCancelledBy(handler: GestureHandler, otherHandler: GestureHandler): Boolean
88
}

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerOrchestrator.kt

Lines changed: 23 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@ class GestureHandlerOrchestrator(
2020
* traversing view hierarchy and looking for gesture handlers.
2121
*/
2222
var minimumAlphaForTraversal = DEFAULT_MIN_ALPHA_FOR_TRAVERSAL
23-
private val gestureHandlers = arrayListOf<GestureHandler<*>>()
24-
private val awaitingHandlers = arrayListOf<GestureHandler<*>>()
25-
private val preparedHandlers = arrayListOf<GestureHandler<*>>()
23+
private val gestureHandlers = arrayListOf<GestureHandler>()
24+
private val awaitingHandlers = arrayListOf<GestureHandler>()
25+
private val preparedHandlers = arrayListOf<GestureHandler>()
2626

2727
// In `onHandlerStateChange` method we iterate through `awaitingHandlers`, but calling `tryActivate` may modify this list.
2828
// To avoid `ConcurrentModificationException` we iterate through copy. There is one more problem though - if handler was
@@ -85,22 +85,20 @@ class GestureHandlerOrchestrator(
8585
finishedHandlersCleanupScheduled = false
8686
}
8787

88-
private fun hasOtherHandlerToWaitFor(handler: GestureHandler<*>) = gestureHandlers.any {
89-
!isFinished(it.state) && shouldHandlerWaitForOther(handler, it)
90-
}
88+
private fun hasOtherHandlerToWaitFor(handler: GestureHandler) =
89+
gestureHandlers.any { !isFinished(it.state) && shouldHandlerWaitForOther(handler, it) }
9190

92-
private fun shouldBeCancelledByFinishedHandler(handler: GestureHandler<*>) = gestureHandlers.any {
93-
shouldHandlerWaitForOther(handler, it) && it.state == GestureHandler.STATE_END
94-
}
91+
private fun shouldBeCancelledByFinishedHandler(handler: GestureHandler) =
92+
gestureHandlers.any { shouldHandlerWaitForOther(handler, it) && it.state == GestureHandler.STATE_END }
9593

96-
private fun shouldBeCancelledByActiveHandler(handler: GestureHandler<*>) = gestureHandlers.any {
94+
private fun shouldBeCancelledByActiveHandler(handler: GestureHandler) = gestureHandlers.any {
9795
handler.hasCommonPointers(it) &&
9896
it.state == GestureHandler.STATE_ACTIVE &&
9997
!canRunSimultaneously(handler, it) &&
10098
handler.isDescendantOf(it)
10199
}
102100

103-
private fun tryActivate(handler: GestureHandler<*>) {
101+
private fun tryActivate(handler: GestureHandler) {
104102
// If we are waiting for a gesture that has successfully finished, we should cancel handler
105103
if (shouldBeCancelledByFinishedHandler(handler) || shouldBeCancelledByActiveHandler(handler)) {
106104
handler.cancel()
@@ -129,7 +127,7 @@ class GestureHandlerOrchestrator(
129127
}
130128

131129
/*package*/
132-
fun onHandlerStateChange(handler: GestureHandler<*>, newState: Int, prevState: Int) {
130+
fun onHandlerStateChange(handler: GestureHandler, newState: Int, prevState: Int) {
133131
handlingChangeSemaphore += 1
134132
if (isFinished(newState)) {
135133
// We have to loop through copy in order to avoid modifying collection
@@ -193,7 +191,7 @@ class GestureHandlerOrchestrator(
193191
scheduleFinishedHandlersCleanup()
194192
}
195193

196-
private fun makeActive(handler: GestureHandler<*>) {
194+
private fun makeActive(handler: GestureHandler) {
197195
val currentState = handler.state
198196
with(handler) {
199197
isAwaiting = false
@@ -270,7 +268,7 @@ class GestureHandlerOrchestrator(
270268
}
271269
}
272270

273-
private fun deliverEventToGestureHandler(handler: GestureHandler<*>, sourceEvent: MotionEvent) {
271+
private fun deliverEventToGestureHandler(handler: GestureHandler, sourceEvent: MotionEvent) {
274272
if (!isViewAttachedUnderWrapper(handler.view)) {
275273
handler.cancel()
276274
return
@@ -424,7 +422,7 @@ class GestureHandlerOrchestrator(
424422
return point
425423
}
426424

427-
private fun addAwaitingHandler(handler: GestureHandler<*>) {
425+
private fun addAwaitingHandler(handler: GestureHandler) {
428426
if (awaitingHandlers.contains(handler)) {
429427
return
430428
}
@@ -438,7 +436,7 @@ class GestureHandlerOrchestrator(
438436
}
439437
}
440438

441-
private fun recordHandlerIfNotPresent(handler: GestureHandler<*>, view: View) {
439+
private fun recordHandlerIfNotPresent(handler: GestureHandler, view: View) {
442440
if (gestureHandlers.contains(handler)) {
443441
return
444442
}
@@ -497,7 +495,7 @@ class GestureHandlerOrchestrator(
497495
// There's only one exception - RootViewGestureHandler. TalkBack uses hover events,
498496
// so we need to pass them into RootViewGestureHandler, otherwise press and hold
499497
// gesture stops working correctly (see https://github.com/software-mansion/react-native-gesture-handler/issues/3407)
500-
private fun shouldHandlerSkipHoverEvents(handler: GestureHandler<*>, action: Int): Boolean {
498+
private fun shouldHandlerSkipHoverEvents(handler: GestureHandler, action: Int): Boolean {
501499
val shouldSkipHoverEvents =
502500
handler !is HoverGestureHandler &&
503501
handler !is RNGestureHandlerRootHelper.RootViewGestureHandler
@@ -672,7 +670,7 @@ class GestureHandlerOrchestrator(
672670
private val matrixTransformCoords = FloatArray(2)
673671
private val inverseMatrix = Matrix()
674672
private val tempCoords = FloatArray(2)
675-
private val handlersComparator = Comparator<GestureHandler<*>> { a, b ->
673+
private val handlersComparator = Comparator<GestureHandler> { a, b ->
676674
return@Comparator if (a.isActive && b.isActive || a.isAwaiting && b.isAwaiting) {
677675
// both A and B are either active or awaiting activation, in which case we prefer one that
678676
// has activated (or turned into "awaiting" state) earlier
@@ -726,17 +724,16 @@ class GestureHandlerOrchestrator(
726724
private fun isTransformedTouchPointInView(x: Float, y: Float, child: View) =
727725
x in 0f..child.width.toFloat() && y in 0f..child.height.toFloat()
728726

729-
private fun shouldHandlerWaitForOther(handler: GestureHandler<*>, other: GestureHandler<*>): Boolean =
730-
handler !== other &&
731-
(
732-
handler.shouldWaitForHandlerFailure(other) ||
733-
other.shouldRequireToWaitForFailure(handler)
734-
)
727+
private fun shouldHandlerWaitForOther(handler: GestureHandler, other: GestureHandler): Boolean =
728+
handler !== other && (
729+
handler.shouldWaitForHandlerFailure(other) ||
730+
other.shouldRequireToWaitForFailure(handler)
731+
)
735732

736-
private fun canRunSimultaneously(a: GestureHandler<*>, b: GestureHandler<*>) =
733+
private fun canRunSimultaneously(a: GestureHandler, b: GestureHandler) =
737734
a === b || a.shouldRecognizeSimultaneously(b) || b.shouldRecognizeSimultaneously(a)
738735

739-
private fun shouldHandlerBeCancelledBy(handler: GestureHandler<*>, other: GestureHandler<*>): Boolean {
736+
private fun shouldHandlerBeCancelledBy(handler: GestureHandler, other: GestureHandler): Boolean {
740737
if (!handler.hasCommonPointers(other)) {
741738
// if two handlers share no common pointer one can never trigger cancel for the other
742739
return false

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/GestureHandlerRegistry.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ import android.view.View
44
import java.util.*
55

66
interface GestureHandlerRegistry {
7-
fun getHandlersForView(view: View): ArrayList<GestureHandler<*>>?
7+
fun getHandlersForView(view: View): ArrayList<GestureHandler>?
88
}

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/HoverGestureHandler.kt

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@ import com.swmansion.gesturehandler.react.RNGestureHandlerRootHelper
1010
import com.swmansion.gesturehandler.react.RNViewConfigurationHelper
1111
import com.swmansion.gesturehandler.react.eventbuilders.HoverGestureHandlerEventDataBuilder
1212

13-
class HoverGestureHandler : GestureHandler<HoverGestureHandler>() {
13+
class HoverGestureHandler : GestureHandler() {
1414
private var handler: Handler? = null
1515
private var finishRunnable = Runnable { finish() }
1616
var stylusData: StylusData = StylusData()
1717
private set
1818

19-
private infix fun isAncestorOf(other: GestureHandler<*>): Boolean {
19+
private infix fun isAncestorOf(other: GestureHandler): Boolean {
2020
var current: View? = other.view
2121

2222
while (current != null) {
@@ -50,15 +50,15 @@ class HoverGestureHandler : GestureHandler<HoverGestureHandler>() {
5050
return null
5151
}
5252

53-
override fun shouldBeCancelledBy(handler: GestureHandler<*>): Boolean {
53+
override fun shouldBeCancelledBy(handler: GestureHandler): Boolean {
5454
if (handler is HoverGestureHandler && !(handler isAncestorOf this)) {
5555
return isViewDisplayedOverAnother(handler.view!!, this.view!!)!!
5656
}
5757

5858
return super.shouldBeCancelledBy(handler)
5959
}
6060

61-
override fun shouldRequireToWaitForFailure(handler: GestureHandler<*>): Boolean {
61+
override fun shouldRequireToWaitForFailure(handler: GestureHandler): Boolean {
6262
if (handler is HoverGestureHandler) {
6363
if (!(this isAncestorOf handler) && !(handler isAncestorOf this)) {
6464
isViewDisplayedOverAnother(this.view!!, handler.view!!)?.let {
@@ -70,10 +70,8 @@ class HoverGestureHandler : GestureHandler<HoverGestureHandler>() {
7070
return super.shouldRequireToWaitForFailure(handler)
7171
}
7272

73-
override fun shouldRecognizeSimultaneously(handler: GestureHandler<*>): Boolean {
74-
if (handler is HoverGestureHandler &&
75-
(this isAncestorOf handler || handler isAncestorOf this)
76-
) {
73+
override fun shouldRecognizeSimultaneously(handler: GestureHandler): Boolean {
74+
if (handler is HoverGestureHandler && (this isAncestorOf handler || handler isAncestorOf this)) {
7775
return true
7876
}
7977

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/LongPressGestureHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import com.facebook.react.bridge.ReadableMap
99
import com.facebook.react.uimanager.PixelUtil
1010
import com.swmansion.gesturehandler.react.eventbuilders.LongPressGestureHandlerEventDataBuilder
1111

12-
class LongPressGestureHandler(context: Context) : GestureHandler<LongPressGestureHandler>() {
12+
class LongPressGestureHandler(context: Context) : GestureHandler() {
1313
var minDurationMs = DEFAULT_MIN_DURATION_MS
1414
val duration: Int
1515
get() = (previousTime - startTime).toInt()

packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/ManualGestureHandler.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import android.content.Context
44
import android.view.MotionEvent
55
import com.swmansion.gesturehandler.react.eventbuilders.ManualGestureHandlerEventDataBuilder
66

7-
class ManualGestureHandler : GestureHandler<ManualGestureHandler>() {
7+
class ManualGestureHandler : GestureHandler() {
88
override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) {
99
if (state == STATE_UNDETERMINED) {
1010
begin()

0 commit comments

Comments
 (0)