Skip to content

Commit 8b717ae

Browse files
authored
Update how handler configuration is handled on Android (#3484)
## Description All handler factories were stored in the `RNGestureHandlerModule` which always seemed like an odd place to keep them in to me. This PR moves each factory to be a nested class of the relevant gesture handler. This gives us private access to the handler inside the factory, which in turn allows to get rid of the setter methods and assign directly instead. Those were with use since RNGH was written in java, but we have Kotlin now. It's time to let go and use proper syntax. It also adds default constants for each config property that didn't have it before. ## Test plan Example app
1 parent 6a8525e commit 8b717ae

11 files changed

+537
-565
lines changed

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

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
package com.swmansion.gesturehandler.core
22

3+
import android.content.Context
34
import android.os.Handler
45
import android.os.Looper
56
import android.view.MotionEvent
67
import android.view.VelocityTracker
8+
import com.facebook.react.bridge.ReadableMap
9+
import com.swmansion.gesturehandler.react.eventbuilders.FlingGestureHandlerEventDataBuilder
710

811
class FlingGestureHandler : GestureHandler<FlingGestureHandler>() {
912
var numberOfPointersRequired = DEFAULT_NUMBER_OF_TOUCHES_REQUIRED
@@ -126,6 +129,32 @@ class FlingGestureHandler : GestureHandler<FlingGestureHandler>() {
126129
event.offsetLocation(-offsetX, -offsetY)
127130
}
128131

132+
class Factory : GestureHandler.Factory<FlingGestureHandler>() {
133+
override val type = FlingGestureHandler::class.java
134+
override val name = "FlingGestureHandler"
135+
136+
override fun create(context: Context?): FlingGestureHandler {
137+
return FlingGestureHandler()
138+
}
139+
140+
override fun setConfig(handler: FlingGestureHandler, config: ReadableMap) {
141+
super.setConfig(handler, config)
142+
if (config.hasKey(KEY_NUMBER_OF_POINTERS)) {
143+
handler.numberOfPointersRequired = config.getInt(KEY_NUMBER_OF_POINTERS)
144+
}
145+
if (config.hasKey(KEY_DIRECTION)) {
146+
handler.direction = config.getInt(KEY_DIRECTION)
147+
}
148+
}
149+
150+
override fun createEventBuilder(handler: FlingGestureHandler) = FlingGestureHandlerEventDataBuilder(handler)
151+
152+
companion object {
153+
private const val KEY_NUMBER_OF_POINTERS = "numberOfPointers"
154+
private const val KEY_DIRECTION = "direction"
155+
}
156+
}
157+
129158
companion object {
130159
private const val DEFAULT_MAX_DURATION_MS: Long = 800
131160
private const val DEFAULT_MIN_VELOCITY: Long = 2000

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

Lines changed: 119 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,15 @@ import android.view.MotionEvent.PointerProperties
1111
import android.view.View
1212
import com.facebook.react.bridge.Arguments
1313
import com.facebook.react.bridge.ReactContext
14+
import com.facebook.react.bridge.ReadableMap
15+
import com.facebook.react.bridge.ReadableType
1416
import com.facebook.react.bridge.UiThreadUtil
1517
import com.facebook.react.bridge.WritableArray
1618
import com.facebook.react.uimanager.PixelUtil
1719
import com.swmansion.gesturehandler.BuildConfig
1820
import com.swmansion.gesturehandler.RNSVGHitTester
1921
import com.swmansion.gesturehandler.react.RNGestureHandlerTouchEvent
22+
import com.swmansion.gesturehandler.react.eventbuilders.GestureHandlerEventDataBuilder
2023
import java.lang.IllegalStateException
2124
import java.util.*
2225

@@ -36,7 +39,17 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
3639
var isWithinBounds = false
3740
private set
3841
var isEnabled = true
39-
private set
42+
private set(enabled) {
43+
// Don't cancel handler when not changing the value of the isEnabled, executing it always caused
44+
// handlers to be cancelled on re-render because that's the moment when the config is updated.
45+
// If the enabled prop "changed" from true to true the handler would get cancelled.
46+
if (view != null && isEnabled != enabled) {
47+
// If view is set then handler is in "active" state. In that case we want to "cancel" handler
48+
// when it changes enabled state so that it gets cleared from the orchestrator
49+
UiThreadUtil.runOnUiThread { cancel() }
50+
}
51+
field = enabled
52+
}
4053
var actionType = 0
4154

4255
var changedTouchesPayload: WritableArray? = null
@@ -62,9 +75,9 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
6275

6376
private var lastEventOffsetX = 0f
6477
private var lastEventOffsetY = 0f
65-
private var shouldCancelWhenOutside = false
6678
var numberOfPointers = 0
67-
private set
79+
protected set
80+
protected var shouldCancelWhenOutside = false
6881
protected var orchestrator: GestureHandlerOrchestrator? = null
6982
private var onTouchEventListener: OnTouchEventListener? = null
7083
private var interactionController: GestureHandlerInteractionController? = null
@@ -100,11 +113,12 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
100113
}
101114

102115
open fun resetConfig() {
103-
needsPointerData = false
104-
manualActivation = false
105-
shouldCancelWhenOutside = false
106-
isEnabled = true
107-
hitSlop = null
116+
needsPointerData = DEFAULT_NEEDS_POINTER_DATA
117+
manualActivation = DEFAULT_MANUAL_ACTIVATION
118+
shouldCancelWhenOutside = DEFAULT_SHOULD_CANCEL_WHEN_OUTSIDE
119+
isEnabled = DEFAULT_IS_ENABLED
120+
hitSlop = DEFAULT_HIT_SLOP
121+
mouseButton = DEFAULT_MOUSE_BUTTON
108122
}
109123

110124
fun hasCommonPointers(other: GestureHandler<*>): Boolean {
@@ -116,24 +130,6 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
116130
return false
117131
}
118132

119-
fun setShouldCancelWhenOutside(shouldCancelWhenOutside: Boolean): ConcreteGestureHandlerT =
120-
applySelf { this.shouldCancelWhenOutside = shouldCancelWhenOutside }
121-
122-
fun setEnabled(enabled: Boolean): ConcreteGestureHandlerT = applySelf {
123-
// Don't cancel handler when not changing the value of the isEnabled, executing it always caused
124-
// handlers to be cancelled on re-render because that's the moment when the config is updated.
125-
// If the enabled prop "changed" from true to true the handler would get cancelled.
126-
if (view != null && isEnabled != enabled) {
127-
// If view is set then handler is in "active" state. In that case we want to "cancel" handler
128-
// when it changes enabled state so that it gets cleared from the orchestrator
129-
UiThreadUtil.runOnUiThread { cancel() }
130-
}
131-
isEnabled = enabled
132-
}
133-
134-
fun setManualActivation(manualActivation: Boolean): ConcreteGestureHandlerT =
135-
applySelf { this.manualActivation = manualActivation }
136-
137133
fun setHitSlop(
138134
leftPad: Float,
139135
topPad: Float,
@@ -164,10 +160,6 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
164160
fun setInteractionController(controller: GestureHandlerInteractionController?): ConcreteGestureHandlerT =
165161
applySelf { interactionController = controller }
166162

167-
fun setMouseButton(mouseButton: Int) = apply {
168-
this.mouseButton = mouseButton
169-
}
170-
171163
fun prepare(view: View?, orchestrator: GestureHandlerOrchestrator?) {
172164
check(!(this.view != null || this.orchestrator != null)) { "Already prepared or hasn't been reset" }
173165
Arrays.fill(trackedPointerIDs, -1)
@@ -822,7 +814,104 @@ open class GestureHandler<ConcreteGestureHandlerT : GestureHandler<ConcreteGestu
822814
val lastPositionInWindowY: Float
823815
get() = lastAbsolutePositionY + lastEventOffsetY - windowOffset[1]
824816

817+
abstract class Factory<T : GestureHandler<T>> {
818+
abstract val type: Class<T>
819+
abstract val name: String
820+
abstract fun create(context: Context?): T
821+
open fun setConfig(handler: T, config: ReadableMap) {
822+
handler.resetConfig()
823+
if (config.hasKey(KEY_SHOULD_CANCEL_WHEN_OUTSIDE)) {
824+
handler.shouldCancelWhenOutside = config.getBoolean(KEY_SHOULD_CANCEL_WHEN_OUTSIDE)
825+
}
826+
if (config.hasKey(KEY_ENABLED)) {
827+
handler.isEnabled = config.getBoolean(KEY_ENABLED)
828+
}
829+
if (config.hasKey(KEY_HIT_SLOP)) {
830+
handleHitSlopProperty(handler, config)
831+
}
832+
if (config.hasKey(KEY_NEEDS_POINTER_DATA)) {
833+
handler.needsPointerData = config.getBoolean(KEY_NEEDS_POINTER_DATA)
834+
}
835+
if (config.hasKey(KEY_MANUAL_ACTIVATION)) {
836+
handler.manualActivation = config.getBoolean(KEY_MANUAL_ACTIVATION)
837+
}
838+
if (config.hasKey(KEY_MOUSE_BUTTON)) {
839+
handler.mouseButton = config.getInt(KEY_MOUSE_BUTTON)
840+
}
841+
}
842+
843+
abstract fun createEventBuilder(handler: T): GestureHandlerEventDataBuilder<T>
844+
845+
companion object {
846+
private const val KEY_SHOULD_CANCEL_WHEN_OUTSIDE = "shouldCancelWhenOutside"
847+
private const val KEY_ENABLED = "enabled"
848+
private const val KEY_NEEDS_POINTER_DATA = "needsPointerData"
849+
private const val KEY_MANUAL_ACTIVATION = "manualActivation"
850+
private const val KEY_MOUSE_BUTTON = "mouseButton"
851+
private const val KEY_HIT_SLOP = "hitSlop"
852+
private const val KEY_HIT_SLOP_LEFT = "left"
853+
private const val KEY_HIT_SLOP_TOP = "top"
854+
private const val KEY_HIT_SLOP_RIGHT = "right"
855+
private const val KEY_HIT_SLOP_BOTTOM = "bottom"
856+
private const val KEY_HIT_SLOP_VERTICAL = "vertical"
857+
private const val KEY_HIT_SLOP_HORIZONTAL = "horizontal"
858+
private const val KEY_HIT_SLOP_WIDTH = "width"
859+
private const val KEY_HIT_SLOP_HEIGHT = "height"
860+
861+
private fun handleHitSlopProperty(handler: GestureHandler<*>, config: ReadableMap) {
862+
if (config.getType(KEY_HIT_SLOP) == ReadableType.Number) {
863+
val hitSlop = PixelUtil.toPixelFromDIP(config.getDouble(KEY_HIT_SLOP))
864+
handler.setHitSlop(hitSlop, hitSlop, hitSlop, hitSlop, GestureHandler.HIT_SLOP_NONE, GestureHandler.HIT_SLOP_NONE)
865+
} else {
866+
val hitSlop = config.getMap(KEY_HIT_SLOP)!!
867+
var left = GestureHandler.HIT_SLOP_NONE
868+
var top = GestureHandler.HIT_SLOP_NONE
869+
var right = GestureHandler.HIT_SLOP_NONE
870+
var bottom = GestureHandler.HIT_SLOP_NONE
871+
var width = GestureHandler.HIT_SLOP_NONE
872+
var height = GestureHandler.HIT_SLOP_NONE
873+
if (hitSlop.hasKey(KEY_HIT_SLOP_HORIZONTAL)) {
874+
val horizontalPad = PixelUtil.toPixelFromDIP(hitSlop.getDouble(KEY_HIT_SLOP_HORIZONTAL))
875+
right = horizontalPad
876+
left = right
877+
}
878+
if (hitSlop.hasKey(KEY_HIT_SLOP_VERTICAL)) {
879+
val verticalPad = PixelUtil.toPixelFromDIP(hitSlop.getDouble(KEY_HIT_SLOP_VERTICAL))
880+
bottom = verticalPad
881+
top = bottom
882+
}
883+
if (hitSlop.hasKey(KEY_HIT_SLOP_LEFT)) {
884+
left = PixelUtil.toPixelFromDIP(hitSlop.getDouble(KEY_HIT_SLOP_LEFT))
885+
}
886+
if (hitSlop.hasKey(KEY_HIT_SLOP_TOP)) {
887+
top = PixelUtil.toPixelFromDIP(hitSlop.getDouble(KEY_HIT_SLOP_TOP))
888+
}
889+
if (hitSlop.hasKey(KEY_HIT_SLOP_RIGHT)) {
890+
right = PixelUtil.toPixelFromDIP(hitSlop.getDouble(KEY_HIT_SLOP_RIGHT))
891+
}
892+
if (hitSlop.hasKey(KEY_HIT_SLOP_BOTTOM)) {
893+
bottom = PixelUtil.toPixelFromDIP(hitSlop.getDouble(KEY_HIT_SLOP_BOTTOM))
894+
}
895+
if (hitSlop.hasKey(KEY_HIT_SLOP_WIDTH)) {
896+
width = PixelUtil.toPixelFromDIP(hitSlop.getDouble(KEY_HIT_SLOP_WIDTH))
897+
}
898+
if (hitSlop.hasKey(KEY_HIT_SLOP_HEIGHT)) {
899+
height = PixelUtil.toPixelFromDIP(hitSlop.getDouble(KEY_HIT_SLOP_HEIGHT))
900+
}
901+
handler.setHitSlop(left, top, right, bottom, width, height)
902+
}
903+
}
904+
}
905+
}
906+
825907
companion object {
908+
private const val DEFAULT_NEEDS_POINTER_DATA = false
909+
private const val DEFAULT_MANUAL_ACTIVATION = false
910+
private const val DEFAULT_SHOULD_CANCEL_WHEN_OUTSIDE = false
911+
private const val DEFAULT_IS_ENABLED = true
912+
private val DEFAULT_HIT_SLOP = null
913+
private const val DEFAULT_MOUSE_BUTTON = 0
914+
826915
const val STATE_UNDETERMINED = 0
827916
const val STATE_FAILED = 1
828917
const val STATE_BEGAN = 2

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package com.swmansion.gesturehandler.core
22

3+
import android.content.Context
34
import android.os.Handler
45
import android.os.Looper
56
import android.view.MotionEvent
67
import android.view.View
78
import android.view.ViewGroup
89
import com.swmansion.gesturehandler.react.RNGestureHandlerRootHelper
910
import com.swmansion.gesturehandler.react.RNViewConfigurationHelper
11+
import com.swmansion.gesturehandler.react.eventbuilders.HoverGestureHandlerEventDataBuilder
1012

1113
class HoverGestureHandler : GestureHandler<HoverGestureHandler>() {
1214
private var handler: Handler? = null
@@ -130,6 +132,17 @@ class HoverGestureHandler : GestureHandler<HoverGestureHandler>() {
130132
}
131133
}
132134

135+
class Factory : GestureHandler.Factory<HoverGestureHandler>() {
136+
override val type = HoverGestureHandler::class.java
137+
override val name = "HoverGestureHandler"
138+
139+
override fun create(context: Context?): HoverGestureHandler {
140+
return HoverGestureHandler()
141+
}
142+
143+
override fun createEventBuilder(handler: HoverGestureHandler) = HoverGestureHandlerEventDataBuilder(handler)
144+
}
145+
133146
companion object {
134147
private val viewConfigHelper = RNViewConfigurationHelper()
135148
}

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

Lines changed: 43 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,16 @@ import android.os.Handler
55
import android.os.Looper
66
import android.os.SystemClock
77
import android.view.MotionEvent
8+
import com.facebook.react.bridge.ReadableMap
9+
import com.facebook.react.uimanager.PixelUtil
10+
import com.swmansion.gesturehandler.react.eventbuilders.LongPressGestureHandlerEventDataBuilder
811

912
class LongPressGestureHandler(context: Context) : GestureHandler<LongPressGestureHandler>() {
1013
var minDurationMs = DEFAULT_MIN_DURATION_MS
1114
val duration: Int
1215
get() = (previousTime - startTime).toInt()
13-
private val defaultMaxDistSq: Float
14-
private var maxDistSq: Float
16+
private val defaultMaxDist: Float
17+
private var maxDist: Float
1518
private var numberOfPointersRequired: Int
1619
private var startX = 0f
1720
private var startY = 0f
@@ -21,28 +24,19 @@ class LongPressGestureHandler(context: Context) : GestureHandler<LongPressGestur
2124
private var currentPointers = 0
2225

2326
init {
24-
setShouldCancelWhenOutside(true)
27+
shouldCancelWhenOutside = true
2528

26-
val defaultMaxDist = DEFAULT_MAX_DIST_DP * context.resources.displayMetrics.density
27-
defaultMaxDistSq = defaultMaxDist * defaultMaxDist
28-
maxDistSq = defaultMaxDistSq
29+
val systemDefaultMaxDist = DEFAULT_MAX_DIST_DP * context.resources.displayMetrics.density
30+
defaultMaxDist = systemDefaultMaxDist
31+
maxDist = defaultMaxDist
2932
numberOfPointersRequired = 1
3033
}
3134

3235
override fun resetConfig() {
3336
super.resetConfig()
3437
minDurationMs = DEFAULT_MIN_DURATION_MS
35-
maxDistSq = defaultMaxDistSq
36-
}
37-
38-
fun setMaxDist(maxDist: Float): LongPressGestureHandler {
39-
maxDistSq = maxDist * maxDist
40-
return this
41-
}
42-
43-
fun setNumberOfPointers(numberOfPointers: Int): LongPressGestureHandler {
44-
numberOfPointersRequired = numberOfPointers
45-
return this
38+
maxDist = defaultMaxDist
39+
shouldCancelWhenOutside = DEFAULT_SHOULD_CANCEL_WHEN_OUTSIDE
4640
}
4741

4842
private fun getAverageCoords(ev: MotionEvent, excludePointer: Boolean = false): Pair<Float, Float> {
@@ -141,7 +135,7 @@ class LongPressGestureHandler(context: Context) : GestureHandler<LongPressGestur
141135
val deltaY = y - startY
142136
val distSq = deltaX * deltaX + deltaY * deltaY
143137

144-
if (distSq > maxDistSq) {
138+
if (distSq > maxDist * maxDist) {
145139
if (state == STATE_ACTIVE) {
146140
cancel()
147141
} else {
@@ -173,7 +167,38 @@ class LongPressGestureHandler(context: Context) : GestureHandler<LongPressGestur
173167
currentPointers = 0
174168
}
175169

170+
class Factory : GestureHandler.Factory<LongPressGestureHandler>() {
171+
override val type = LongPressGestureHandler::class.java
172+
override val name = "LongPressGestureHandler"
173+
174+
override fun create(context: Context?): LongPressGestureHandler {
175+
return LongPressGestureHandler((context)!!)
176+
}
177+
178+
override fun setConfig(handler: LongPressGestureHandler, config: ReadableMap) {
179+
super.setConfig(handler, config)
180+
if (config.hasKey(KEY_MIN_DURATION_MS)) {
181+
handler.minDurationMs = config.getInt(KEY_MIN_DURATION_MS).toLong()
182+
}
183+
if (config.hasKey(KEY_MAX_DIST)) {
184+
handler.maxDist = PixelUtil.toPixelFromDIP(config.getDouble(KEY_MAX_DIST))
185+
}
186+
if (config.hasKey(KEY_NUMBER_OF_POINTERS)) {
187+
handler.numberOfPointers = config.getInt(KEY_NUMBER_OF_POINTERS)
188+
}
189+
}
190+
191+
override fun createEventBuilder(handler: LongPressGestureHandler) = LongPressGestureHandlerEventDataBuilder(handler)
192+
193+
companion object {
194+
private const val KEY_MIN_DURATION_MS = "minDurationMs"
195+
private const val KEY_MAX_DIST = "maxDist"
196+
private const val KEY_NUMBER_OF_POINTERS = "numberOfPointers"
197+
}
198+
}
199+
176200
companion object {
201+
private const val DEFAULT_SHOULD_CANCEL_WHEN_OUTSIDE = true
177202
private const val DEFAULT_MIN_DURATION_MS: Long = 500
178203
private const val DEFAULT_MAX_DIST_DP = 10f
179204
}

0 commit comments

Comments
 (0)