SIGN IN SIGN UP

[Android] Fix native handlers attaching to a nested button instead of the detector's child (#4464)

## Description

`tryFindGestureHandlerButton` was added in #3634 to find the button
inside the wrapper `View` of the `display: contents` sandwich. #4044
replaced that structure with a single-view button, so the correct target
is the detector's direct child again - but the search was left in and
still fired whenever the child's first child happened to be a bare
`ButtonViewGroup` (e.g. `Pressable` or `Touchable` as the first child of
a button or of a view under a native-gesture detector), attaching the
handler to that inner button instead.

Before this change, the outer button in the test screen did not react to
presses anywhere except over the inner pressable, and pressing the inner
pressable fired the outer handler's callbacks alongside the inner ones
(with a doubled `pressIn` on the inner pressable).

This PR removes the search so native handlers always attach to the
detector's child, with the existing exception of `RefreshControl`
unwrapping.

## Test plan

Compared builds from this branch and its base commit on the Android
emulator using the test screen below:

<details>
<summary>Test screen</summary>

```tsx
import React, { useCallback, useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import {
  GestureDetector,
  Pressable,
  RawButton,
  RefreshControl,
  ScrollView,
  useNativeGesture,
} from 'react-native-gesture-handler';

export default function EmptyExample() {
  const [log, setLog] = useState<string[]>([]);

  const append = useCallback((entry: string) => {
    setLog((prev) => [entry, ...prev].slice(0, 10));
  }, []);

  const [refreshing, setRefreshing] = useState(false);
  const onRefresh = () => {
    append('3. refresh triggered');
    setRefreshing(true);
    setTimeout(() => setRefreshing(false), 1500);
  };

  const wrappedViewGesture = useNativeGesture({
    disableReanimated: true,
    onBegin: () => append('2. native gesture on View: begin'),
    onActivate: () => append('2. native gesture on View: activate'),
    onFinalize: () => append('2. native gesture on View: finalize'),
  });

  return (
    <View style={styles.container}>
      <View style={styles.logPane}>
        {log.length === 0 ? (
          <Text style={styles.logEntry}>-- log --</Text>
        ) : (
          log.map((entry, i) => (
            <Text key={`${i}-${entry}`} style={styles.logEntry}>
              {entry}
            </Text>
          ))
        )}
      </View>
      <ScrollView
        style={styles.scroll}
        refreshControl={
          <RefreshControl refreshing={refreshing} onRefresh={onRefresh} />
        }>
        <Text style={styles.title}>1. Button with a bare button inside</Text>
        <RawButton
          style={styles.outerButton}
          rippleColor="#88a"
          onBegin={() => append('1. outer button: begin')}
          onActivate={() => append('1. outer button: activate')}
          onFinalize={() => append('1. outer button: finalize')}>
          <Pressable
            style={styles.innerButton}
            onPressIn={() => append('1. inner pressable: pressIn')}
            onPress={() => append('1. inner pressable: press')}>
            <Text>Inner Pressable (first child)</Text>
          </Pressable>
          <Text>Outer button area</Text>
        </RawButton>

        <Text style={styles.title}>2. Native gesture on a plain View</Text>
        <GestureDetector gesture={wrappedViewGesture}>
          <View style={styles.wrappedView}>
            <Pressable
              style={styles.innerButton}
              onPress={() => append('2. inner pressable: press')}>
              <Text>Inner Pressable (first child)</Text>
            </Pressable>
            <Text>Plain View area</Text>
          </View>
        </GestureDetector>

        <Text style={styles.title}>3. Pull to refresh</Text>
        {Array.from({ length: 15 }, (_, i) => (
          <View key={i} style={styles.row}>
            <Text>Row {i + 1}</Text>
          </View>
        ))}
      </ScrollView>
    </View>
  );
}

const styles = StyleSheet.create({
  container: { flex: 1 },
  logPane: { minHeight: 170, padding: 8, backgroundColor: '#222' },
  logEntry: { color: '#eee', fontSize: 12, fontVariant: ['tabular-nums'] },
  scroll: { flex: 1 },
  title: { fontSize: 16, fontWeight: 'bold', marginHorizontal: 12, marginTop: 16, marginBottom: 8 },
  outerButton: { backgroundColor: '#ccd5ff', marginHorizontal: 12, padding: 24, borderRadius: 8 },
  wrappedView: { backgroundColor: '#cdf5cd', marginHorizontal: 12, padding: 24, borderRadius: 8 },
  innerButton: {
    backgroundColor: '#f5c6c6',
    padding: 12,
    borderRadius: 6,
    marginBottom: 8,
    alignSelf: 'flex-start',
  },
  row: {
    height: 44,
    justifyContent: 'center',
    paddingHorizontal: 12,
    borderBottomWidth: StyleSheet.hairlineWidth,
    borderBottomColor: '#ccc',
  },
});
```

</details>
M
Michał Bert committed
0d25288c7be28b56e2276eeabd3db4a721e3fdae
Parent: d3547ac
Committed by GitHub <noreply@github.com> on 8/26/2026, 2:13:46 PM