Skip to content

Commit e72ae7e

Browse files
committed
Route :active and :active-deepest through a shared touch listener
A View exposes a single OnTouchListener slot, so attaching both :active and :active-deepest to one view made whichever selector registered second overwrite the first. Funnel both through one shared listener keyed by activeCallbacks and deepestCallbacks so they coexist.
1 parent 9c2e3b0 commit e72ae7e

1 file changed

Lines changed: 41 additions & 34 deletions

File tree

packages/react-native-reanimated/android/src/main/java/com/swmansion/reanimated/pseudoSelectors/PseudoSelectorManager.kt

Lines changed: 41 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,9 @@ class PseudoSelectorManager(
1414
private val detachActions = HashMap<String, Runnable>()
1515

1616
private val activeCallbacks = LinkedHashMap<View, PseudoSelectorCallback>()
17+
private val deepestCallbacks = LinkedHashMap<View, PseudoSelectorCallback>()
1718

18-
private val activeDeepestViews = LinkedHashSet<View>()
19+
private val touchListenerViews = HashSet<View>()
1920

2021
fun attach(
2122
tag: Int,
@@ -104,21 +105,11 @@ class PseudoSelectorManager(
104105
callback: PseudoSelectorCallback,
105106
) {
106107
activeCallbacks[view] = callback
107-
val listener =
108-
View.OnTouchListener { _, event ->
109-
val action = event.actionMasked
110-
if (action == MotionEvent.ACTION_DOWN) {
111-
fireActiveCallbacksUpTree(view, true)
112-
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
113-
fireActiveCallbacksUpTree(view, false)
114-
}
115-
false
116-
}
117-
view.setOnTouchListener(listener)
108+
ensureTouchListener(view)
118109
detachActions[key] =
119110
Runnable {
120111
activeCallbacks.remove(view)
121-
view.setOnTouchListener(null)
112+
maybeRemoveTouchListener(view)
122113
}
123114
}
124115

@@ -127,27 +118,43 @@ class PseudoSelectorManager(
127118
key: String,
128119
callback: PseudoSelectorCallback,
129120
) {
130-
activeDeepestViews.add(view)
131-
val listener =
132-
View.OnTouchListener { _, event ->
133-
val action = event.actionMasked
134-
if (action == MotionEvent.ACTION_DOWN) {
135-
if (!hasDeepestDescendantAt(view, event.rawX, event.rawY)) {
136-
callback.onSelectorStateChanged(true)
137-
}
121+
deepestCallbacks[view] = callback
122+
ensureTouchListener(view)
123+
detachActions[key] =
124+
Runnable {
125+
deepestCallbacks.remove(view)
126+
maybeRemoveTouchListener(view)
127+
}
128+
}
129+
130+
private fun ensureTouchListener(view: View) {
131+
if (!touchListenerViews.add(view)) {
132+
return
133+
}
134+
view.setOnTouchListener { _, event ->
135+
when (event.actionMasked) {
136+
MotionEvent.ACTION_DOWN -> {
138137
fireActiveCallbacksUpTree(view, true)
139-
} else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
140-
callback.onSelectorStateChanged(false)
138+
deepestCallbacks[view]?.let {
139+
if (!hasDeepestDescendantAt(view, event.rawX, event.rawY)) {
140+
it.onSelectorStateChanged(true)
141+
}
142+
}
143+
}
144+
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
141145
fireActiveCallbacksUpTree(view, false)
146+
deepestCallbacks[view]?.onSelectorStateChanged(false)
142147
}
143-
false
144-
}
145-
view.setOnTouchListener(listener)
146-
detachActions[key] =
147-
Runnable {
148-
activeDeepestViews.remove(view)
149-
view.setOnTouchListener(null)
150148
}
149+
false
150+
}
151+
}
152+
153+
private fun maybeRemoveTouchListener(view: View) {
154+
if (view !in activeCallbacks && view !in deepestCallbacks) {
155+
touchListenerViews.remove(view)
156+
view.setOnTouchListener(null)
157+
}
151158
}
152159

153160
fun detach(
@@ -158,9 +165,9 @@ class PseudoSelectorManager(
158165
}
159166

160167
/**
161-
* Returns true if any view in `activeDeepestViews` is a strict descendant of `ancestor`
162-
* and contains the screen point (`rawX`, `rawY`).
163-
* Used by _:active-deepest_ to yield priority to deeper registered views.
168+
* Returns true if any view registered for _:active-deepest_ is a strict descendant of
169+
* `ancestor` and contains the screen point (`rawX`, `rawY`), so the ancestor yields priority
170+
* to the deeper view.
164171
*/
165172
private fun hasDeepestDescendantAt(
166173
ancestor: View,
@@ -169,7 +176,7 @@ class PseudoSelectorManager(
169176
): Boolean {
170177
val loc = IntArray(2)
171178
// TODO: Optimize so we don't iterate over all the views with :active-deepest every time.
172-
for (candidate in activeDeepestViews) {
179+
for (candidate in deepestCallbacks.keys) {
173180
if (candidate === ancestor) continue
174181
if (!isDescendantOf(candidate, ancestor)) continue
175182
candidate.getLocationOnScreen(loc)

0 commit comments

Comments
 (0)