-
-
Notifications
You must be signed in to change notification settings - Fork 982
/
RNGestureHandlerRootHelper.kt
145 lines (133 loc) · 5.26 KB
/
RNGestureHandlerRootHelper.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
package com.swmansion.gesturehandler.react
import android.os.SystemClock
import android.util.Log
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.ViewParent
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.UiThreadUtil
import com.facebook.react.common.ReactConstants
import com.facebook.react.uimanager.RootView
import com.facebook.react.uimanager.ThemedReactContext
import com.swmansion.gesturehandler.core.GestureHandler
import com.swmansion.gesturehandler.core.GestureHandlerOrchestrator
class RNGestureHandlerRootHelper(private val context: ReactContext, wrappedView: ViewGroup) {
private val orchestrator: GestureHandlerOrchestrator?
private val jsGestureHandler: GestureHandler<*>?
val rootView: ViewGroup
private var shouldIntercept = false
private var passingTouch = false
init {
UiThreadUtil.assertOnUiThread()
val wrappedViewTag = wrappedView.id
check(wrappedViewTag >= 1) { "Expect view tag to be set for $wrappedView" }
val module = (context as ThemedReactContext).reactApplicationContext.getNativeModule(RNGestureHandlerModule::class.java)!!
val registry = module.registry
rootView = findRootViewTag(wrappedView)
Log.i(
ReactConstants.TAG,
"[GESTURE HANDLER] Initialize gesture handler for root view $rootView"
)
orchestrator = GestureHandlerOrchestrator(
wrappedView, registry, RNViewConfigurationHelper()
).apply {
minimumAlphaForTraversal = MIN_ALPHA_FOR_TOUCH
}
jsGestureHandler = RootViewGestureHandler().apply { tag = -wrappedViewTag }
with(registry) {
registerHandler(jsGestureHandler)
attachHandlerToView(jsGestureHandler.tag, wrappedViewTag, GestureHandler.ACTION_TYPE_JS_FUNCTION_OLD_API)
}
module.registerRootHelper(this)
}
fun tearDown() {
Log.i(
ReactConstants.TAG,
"[GESTURE HANDLER] Tearing down gesture handler registered for root view $rootView"
)
val module = (context as ThemedReactContext).reactApplicationContext.getNativeModule(RNGestureHandlerModule::class.java)!!
with(module) {
registry.dropHandler(jsGestureHandler!!.tag)
unregisterRootHelper(this@RNGestureHandlerRootHelper)
}
}
private inner class RootViewGestureHandler : GestureHandler<RootViewGestureHandler>() {
override fun onHandle(event: MotionEvent, sourceEvent: MotionEvent) {
val currentState = state
// we shouldn't stop intercepting events when there is an active handler already, which could happen when
// adding a new pointer to the screen after a handler activates
if (currentState == STATE_UNDETERMINED && (!shouldIntercept || orchestrator?.isAnyHandlerActive() != true)) {
begin()
shouldIntercept = false
}
if (event.actionMasked == MotionEvent.ACTION_UP) {
end()
}
}
override fun onCancel() {
shouldIntercept = true
val time = SystemClock.uptimeMillis()
val event = MotionEvent.obtain(time, time, MotionEvent.ACTION_CANCEL, 0f, 0f, 0).apply {
action = MotionEvent.ACTION_CANCEL
}
if (rootView is RootView) {
rootView.onChildStartedNativeGesture(rootView, event)
}
event.recycle()
}
}
fun requestDisallowInterceptTouchEvent() {
// If this method gets called it means that some native view is attempting to grab lock for
// touch event delivery. In that case we cancel all gesture recognizers
if (orchestrator != null && !passingTouch) {
// if we are in the process of delivering touch events via GH orchestrator, we don't want to
// treat it as a native gesture capturing the lock
tryCancelAllHandlers()
}
}
fun dispatchTouchEvent(ev: MotionEvent): Boolean {
// We mark `mPassingTouch` before we get into `mOrchestrator.onTouchEvent` so that we can tell
// if `requestDisallow` has been called as a result of a normal gesture handling process or
// as a result of one of the gesture handlers activating
passingTouch = true
orchestrator!!.onTouchEvent(ev)
passingTouch = false
return shouldIntercept
}
private fun tryCancelAllHandlers() {
// In order to cancel handlers we activate handler that is hooked to the root view
jsGestureHandler?.apply {
if (state == GestureHandler.STATE_BEGAN) {
// Try activate main JS handler
activate()
end()
}
}
}
/*package*/
@Suppress("UNUSED_PARAMETER", "COMMENT_IN_SUPPRESSION")
// We want to keep order of parameters, so instead of removing viewTag we suppress the warning
fun handleSetJSResponder(viewTag: Int, blockNativeResponder: Boolean) {
if (blockNativeResponder) {
UiThreadUtil.runOnUiThread { tryCancelAllHandlers() }
}
}
fun activateNativeHandlers(view: View) {
orchestrator?.activateNativeHandlersForView(view)
}
companion object {
private const val MIN_ALPHA_FOR_TOUCH = 0.1f
private fun findRootViewTag(viewGroup: ViewGroup): ViewGroup {
UiThreadUtil.assertOnUiThread()
var parent: ViewParent? = viewGroup
while (parent != null && parent !is RootView) {
parent = parent.parent
}
checkNotNull(parent) {
"View $viewGroup has not been mounted under ReactRootView"
}
return parent as ViewGroup
}
}
}