Skip to content

Commit b9f236c

Browse files
committed
Allow GestureHandlerRootView to be manually made active
1 parent 578ccb9 commit b9f236c

7 files changed

Lines changed: 61 additions & 6 deletions

File tree

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

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import android.view.MotionEvent
66
import android.view.View
77
import android.view.ViewGroup
88
import android.widget.EditText
9+
import com.swmansion.gesturehandler.react.RNGestureHandlerRootView
910
import java.util.*
1011

1112
class GestureHandlerOrchestrator(
@@ -497,6 +498,17 @@ class GestureHandlerOrchestrator(
497498
}
498499

499500
private fun extractGestureHandlers(viewGroup: ViewGroup, coords: FloatArray, pointerId: Int): Boolean {
501+
if (viewGroup is RNGestureHandlerRootView && viewGroup != wrapperView && viewGroup.isActive()) {
502+
// When we encounter another active root view while traversing the view hierarchy, we want
503+
// to stop there so that it can handle the gesture attached under it itself.
504+
// This helps in cases where a view may call `requestDisallowInterceptTouchEvent` (which would
505+
// cancel all gestures handled by its parent root view) but there may be some gestures attached
506+
// to views under it which should work. Adding another root view under that particular view
507+
// would allow the gesture to be recognized even though the parent root view cancelled its gestures.
508+
// We want to stop here so the gesture receives event only once.
509+
return false
510+
}
511+
500512
val childrenCount = viewGroup.childCount
501513
for (i in childrenCount - 1 downTo 0) {
502514
val child = viewConfigHelper.getChildInDrawingOrderAtIndex(viewGroup, i)
@@ -523,8 +535,19 @@ class GestureHandlerOrchestrator(
523535
return false
524536
}
525537

526-
private fun traverseWithPointerEvents(view: View, coords: FloatArray, pointerId: Int): Boolean =
527-
when (viewConfigHelper.getPointerEventsConfigForView(view)) {
538+
private fun traverseWithPointerEvents(view: View, coords: FloatArray, pointerId: Int): Boolean {
539+
if (view is RNGestureHandlerRootView && view != wrapperView && view.isActive()) {
540+
// When we encounter another active root view while traversing the view hierarchy, we want
541+
// to stop there so that it can handle the gesture attached under it itself.
542+
// This helps in cases where a view may call `requestDisallowInterceptTouchEvent` (which would
543+
// cancel all gestures handled by its parent root view) but there may be some gestures attached
544+
// to views under it which should work. Adding another root view under that particular view
545+
// would allow the gesture to be recognized even though the parent root view cancelled its gestures.
546+
// We want to stop here so the gesture receives event only once.
547+
return false
548+
}
549+
550+
return when (viewConfigHelper.getPointerEventsConfigForView(view)) {
528551
PointerEventsConfig.NONE -> {
529552
// This view and its children can't be the target
530553
false
@@ -569,6 +592,7 @@ class GestureHandlerOrchestrator(
569592
)
570593
}
571594
}
595+
}
572596

573597
private fun canReceiveEvents(view: View) =
574598
view.visibility == View.VISIBLE && view.alpha >= minimumAlphaForTraversal

android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootView.kt

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ import com.facebook.react.views.view.ReactViewGroup
1212

1313
class RNGestureHandlerRootView(context: Context?) : ReactViewGroup(context) {
1414
private var _enabled = false
15+
private var active = false
1516
private var rootHelper: RNGestureHandlerRootHelper? = null // TODO: resettable lateinit
17+
1618
override fun onAttachedToWindow() {
1719
super.onAttachedToWindow()
18-
_enabled = !hasGestureHandlerEnabledRootView(this)
20+
_enabled = active || !hasGestureHandlerEnabledRootView(this)
1921
if (!_enabled) {
2022
Log.i(
2123
ReactConstants.TAG,
@@ -43,6 +45,17 @@ class RNGestureHandlerRootView(context: Context?) : ReactViewGroup(context) {
4345
super.requestDisallowInterceptTouchEvent(disallowIntercept)
4446
}
4547

48+
// Those methods refer to different variables because we need to enforce at least
49+
// one of the Root Views to be enabled to intecept events. By default only the
50+
// top-most view is enabled, but users may want to enable more views to be able
51+
// to work around some native views calling `requestDisallowInterceptTouchEvent`,
52+
// which prevents gestures from working.
53+
fun isActive() = _enabled
54+
55+
fun setActive(active: Boolean) {
56+
this.active = active
57+
}
58+
4659
companion object {
4760
private fun hasGestureHandlerEnabledRootView(viewGroup: ViewGroup): Boolean {
4861
UiThreadUtil.assertOnUiThread()

android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerRootViewManager.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.facebook.react.module.annotations.ReactModule
44
import com.facebook.react.uimanager.ThemedReactContext
55
import com.facebook.react.uimanager.ViewGroupManager
66
import com.facebook.react.uimanager.ViewManagerDelegate
7+
import com.facebook.react.uimanager.annotations.ReactProp
78
import com.facebook.react.viewmanagers.RNGestureHandlerRootViewManagerDelegate
89
import com.facebook.react.viewmanagers.RNGestureHandlerRootViewManagerInterface
910

@@ -34,6 +35,11 @@ class RNGestureHandlerRootViewManager :
3435
view.tearDown()
3536
}
3637

38+
@ReactProp(name = "active")
39+
override fun setActive(view: RNGestureHandlerRootView, active: Boolean) {
40+
view.setActive(active)
41+
}
42+
3743
/**
3844
* The following event configuration is necessary even if you are not using
3945
* GestureHandlerRootView component directly.

android/src/paper/java/com/facebook/react/viewmanagers/RNGestureHandlerRootViewManagerDelegate.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,12 @@ public RNGestureHandlerRootViewManagerDelegate(U viewManager) {
2020
}
2121
@Override
2222
public void setProperty(T view, String propName, @Nullable Object value) {
23-
super.setProperty(view, propName, value);
23+
switch (propName) {
24+
case "active":
25+
mViewManager.setActive(view, value == null ? false : (boolean) value);
26+
break;
27+
default:
28+
super.setProperty(view, propName, value);
29+
}
2430
}
2531
}

android/src/paper/java/com/facebook/react/viewmanagers/RNGestureHandlerRootViewManagerInterface.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,5 @@
1212
import android.view.View;
1313

1414
public interface RNGestureHandlerRootViewManagerInterface<T extends View> {
15-
// No props
15+
void setActive(T view, boolean value);
1616
}

docs/docs/installation.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,10 @@ Note that `GestureHandlerRootView` acts like a normal `View`. So if you want it
6666
If you're using gesture handler in your component library, you may want to wrap your library's code in the GestureHandlerRootView component. This will avoid extra configuration for the user.
6767
:::
6868

69+
:::tip
70+
If you're having trouble with gestures not working when inside a component provided by a third-party library, even though you've wrapped the entry point with `<GestureHandlerRootView>`, you can try adding another `<GestureHandlerRootView active>` closer to the place the gestures are defined. This way, you can prevent Android from canceling relevant gestures when one of the native views tries to grab lock for delivering touch events.
71+
:::
72+
6973
### Linking
7074

7175
> **Important**: You only need to do this step if you're using React Native 0.59 or lower. Since v0.60, linking happens automatically.
Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
22
import type { ViewProps } from 'react-native';
33

4-
interface NativeProps extends ViewProps {}
4+
interface NativeProps extends ViewProps {
5+
active?: boolean;
6+
}
57

68
export default codegenNativeComponent<NativeProps>('RNGestureHandlerRootView');

0 commit comments

Comments
 (0)