Skip to content

Commit 2de57ac

Browse files
authored
[Android] Fix minDistance being reset by partial config updates (#4347)
## Description Follow-up to #4327 On Android, `Pan`'s `updateConfig` derived `hasCustomActivationCriteria` from a local variable recomputed from the keys present in the incoming config map. Config updates can be partial a `SharedValue` used in the config sends `{ [key]: value }` through `updateGestureHandlerConfig`, which doesn't reset the handler first - so the flag reflected a single update message instead of the accumulated configuration. Any criteria-key update (velocity/offset) then hit the "custom criteria and no explicit `minDist`" branch and masked an explicitly configured `minDistance` with `Float.MAX_VALUE`, disabling distance-based activation entirely. `hasCustomActivationCriteria` is now a field on the handler, together with `hasExplicitMinDist` tracking whether `minDist` was explicitly configured; both are cleared in `resetConfig`. This mirrors how the other platforms already model this state (web: instance field + stored `minDist`; iOS: flag recomputed from stored recognizer state in `updateHasCustomActivationCriteria`), which is why they don't exhibit the bug. Full-config paths (`createGestureHandler` / `setGestureHandlerConfig`) go through `resetConfig` first, so their behavior is unchanged. ## Test plan <details> <summary>Tested on the following code:</summary> ```tsx import React, { useState } from 'react'; import { Button, StyleSheet, Text, View } from 'react-native'; import { GestureDetector, usePanGesture } from 'react-native-gesture-handler'; import { useSharedValue } from 'react-native-reanimated'; export default function MinDistReset() { const [log, setLog] = useState<string[]>([]); const minVelocityY = useSharedValue(10000); const append = (entry: string) => setLog((prev) => [...prev.slice(-8), entry]); const pan = usePanGesture({ minDistance: 50, minVelocityY, runOnJS: true, onActivate: () => { append('ACTIVATED'); }, onFinalize: (e) => { if (e.canceled) { append('finished without activation'); } }, }); return ( <View style={styles.container}> <Text style={styles.title}> minDistance = 50, minVelocityY = SharedValue(10000) </Text> <Text style={styles.hint}> Slow 50pt+ drag should ACTIVATE — also after poking the shared value. </Text> <Button title="poke shared value" onPress={() => { // Render-silent on purpose - see the note at the top of the file. minVelocityY.value = minVelocityY.value === 9000 ? 9001 : 9000; console.log('poked: minVelocityY ->', minVelocityY.value); }} /> <GestureDetector gesture={pan}> <View style={styles.box} testID="panBox"> <Text style={styles.boxLabel}>drag here</Text> </View> </GestureDetector> <View style={styles.log}> {log.map((entry, i) => ( <Text key={i} style={styles.logLine}> {entry} </Text> ))} </View> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', paddingTop: 16, }, title: { fontSize: 16, fontWeight: 'bold', }, hint: { textAlign: 'center', marginVertical: 12, color: '#666', }, box: { width: 300, height: 300, backgroundColor: 'tomato', borderRadius: 16, justifyContent: 'center', alignItems: 'center', marginTop: 12, }, boxLabel: { color: 'white', fontSize: 18, }, log: { marginTop: 20, minHeight: 160, alignSelf: 'stretch', paddingHorizontal: 24, }, logLine: { fontFamily: 'monospace', fontSize: 13, }, }); ``` </details>
1 parent bd9650f commit 2de57ac

1 file changed

Lines changed: 23 additions & 14 deletions

File tree

  • packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core

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

Lines changed: 23 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ class PanGestureHandler(context: Context?) : GestureHandler() {
2727

2828
private val defaultMinDist: Float
2929
private var minDist = MAX_VALUE_IGNORE
30+
31+
// Config updates may be partial (e.g. a single key sent when a shared value used in the config
32+
// changes), so both of these need to persist between updateConfig calls - deriving them from the
33+
// keys present in a single update would reset minDist based on an incomplete picture.
34+
private var hasCustomActivationCriteria = false
35+
private var hasExplicitMinDist = false
3036
private var activeOffsetXStart = MIN_VALUE_IGNORE
3137
private var activeOffsetXEnd = MAX_VALUE_IGNORE
3238
private var failOffsetXStart = MAX_VALUE_IGNORE
@@ -90,6 +96,8 @@ class PanGestureHandler(context: Context?) : GestureHandler() {
9096
minVelocityY = DEFAULT_MIN_VELOCITY_Y
9197
minVelocity = DEFAULT_MIN_VELOCITY
9298
minDist = defaultMinDist
99+
hasCustomActivationCriteria = false
100+
hasExplicitMinDist = false
93101
minPointers = DEFAULT_MIN_POINTERS
94102
maxPointers = DEFAULT_MAX_POINTERS
95103
activateAfterLongPress = DEFAULT_ACTIVATE_AFTER_LONG_PRESS
@@ -270,15 +278,14 @@ class PanGestureHandler(context: Context?) : GestureHandler() {
270278

271279
override fun updateConfig(handler: PanGestureHandler, config: ReadableMap) {
272280
super.updateConfig(handler, config)
273-
var hasCustomActivationCriteria = false
274281
if (config.hasKey(KEY_ACTIVE_OFFSET_X_START)) {
275282
handler.activeOffsetXStart =
276283
PixelUtil.toPixelFromDIP(
277284
config.getDouble(
278285
KEY_ACTIVE_OFFSET_X_START,
279286
),
280287
)
281-
hasCustomActivationCriteria = true
288+
handler.hasCustomActivationCriteria = true
282289
}
283290
if (config.hasKey(KEY_ACTIVE_OFFSET_X_END)) {
284291
handler.activeOffsetXEnd =
@@ -287,7 +294,7 @@ class PanGestureHandler(context: Context?) : GestureHandler() {
287294
KEY_ACTIVE_OFFSET_X_END,
288295
),
289296
)
290-
hasCustomActivationCriteria = true
297+
handler.hasCustomActivationCriteria = true
291298
}
292299
if (config.hasKey(KEY_FAIL_OFFSET_RANGE_X_START)) {
293300
handler.failOffsetXStart =
@@ -296,7 +303,7 @@ class PanGestureHandler(context: Context?) : GestureHandler() {
296303
KEY_FAIL_OFFSET_RANGE_X_START,
297304
),
298305
)
299-
hasCustomActivationCriteria = true
306+
handler.hasCustomActivationCriteria = true
300307
}
301308
if (config.hasKey(KEY_FAIL_OFFSET_RANGE_X_END)) {
302309
handler.failOffsetXEnd =
@@ -305,7 +312,7 @@ class PanGestureHandler(context: Context?) : GestureHandler() {
305312
KEY_FAIL_OFFSET_RANGE_X_END,
306313
),
307314
)
308-
hasCustomActivationCriteria = true
315+
handler.hasCustomActivationCriteria = true
309316
}
310317
if (config.hasKey(KEY_ACTIVE_OFFSET_Y_START)) {
311318
handler.activeOffsetYStart =
@@ -314,7 +321,7 @@ class PanGestureHandler(context: Context?) : GestureHandler() {
314321
KEY_ACTIVE_OFFSET_Y_START,
315322
),
316323
)
317-
hasCustomActivationCriteria = true
324+
handler.hasCustomActivationCriteria = true
318325
}
319326
if (config.hasKey(KEY_ACTIVE_OFFSET_Y_END)) {
320327
handler.activeOffsetYEnd =
@@ -323,7 +330,7 @@ class PanGestureHandler(context: Context?) : GestureHandler() {
323330
KEY_ACTIVE_OFFSET_Y_END,
324331
),
325332
)
326-
hasCustomActivationCriteria = true
333+
handler.hasCustomActivationCriteria = true
327334
}
328335
if (config.hasKey(KEY_FAIL_OFFSET_RANGE_Y_START)) {
329336
handler.failOffsetYStart =
@@ -332,7 +339,7 @@ class PanGestureHandler(context: Context?) : GestureHandler() {
332339
KEY_FAIL_OFFSET_RANGE_Y_START,
333340
),
334341
)
335-
hasCustomActivationCriteria = true
342+
handler.hasCustomActivationCriteria = true
336343
}
337344
if (config.hasKey(KEY_FAIL_OFFSET_RANGE_Y_END)) {
338345
handler.failOffsetYEnd =
@@ -341,28 +348,30 @@ class PanGestureHandler(context: Context?) : GestureHandler() {
341348
KEY_FAIL_OFFSET_RANGE_Y_END,
342349
),
343350
)
344-
hasCustomActivationCriteria = true
351+
handler.hasCustomActivationCriteria = true
345352
}
346353
if (config.hasKey(KEY_MIN_VELOCITY)) {
347354
// This value is actually in DPs/ms, but we can use the same function as for converting
348355
// from DPs to pixels as the unit we're converting is in the numerator
349356
handler.minVelocity = PixelUtil.toPixelFromDIP(config.getDouble(KEY_MIN_VELOCITY))
350-
hasCustomActivationCriteria = true
357+
handler.hasCustomActivationCriteria = true
351358
}
352359
if (config.hasKey(KEY_MIN_VELOCITY_X)) {
353360
handler.minVelocityX = PixelUtil.toPixelFromDIP(config.getDouble(KEY_MIN_VELOCITY_X))
354-
hasCustomActivationCriteria = true
361+
handler.hasCustomActivationCriteria = true
355362
}
356363
if (config.hasKey(KEY_MIN_VELOCITY_Y)) {
357364
handler.minVelocityY = PixelUtil.toPixelFromDIP(config.getDouble(KEY_MIN_VELOCITY_Y))
358-
hasCustomActivationCriteria = true
365+
handler.hasCustomActivationCriteria = true
359366
}
360367

361368
// PanGestureHandler sets minDist by default, if there are custom criteria specified we want
362-
// to reset that setting and use provided criteria instead.
369+
// to reset that setting and use provided criteria instead - unless minDist was explicitly
370+
// configured, in which case it must survive partial config updates of other criteria.
363371
if (config.hasKey(KEY_MIN_DIST)) {
364372
handler.minDist = PixelUtil.toPixelFromDIP(config.getDouble(KEY_MIN_DIST))
365-
} else if (hasCustomActivationCriteria) {
373+
handler.hasExplicitMinDist = true
374+
} else if (handler.hasCustomActivationCriteria && !handler.hasExplicitMinDist) {
366375
handler.minDist = Float.MAX_VALUE
367376
}
368377
if (config.hasKey(KEY_MIN_POINTERS)) {

0 commit comments

Comments
 (0)