Skip to content

[Android] Improve TalkBack support#4222

Open
j-piasecki wants to merge 1 commit into
mainfrom
@jpiasecki/improve-talkback-situation
Open

[Android] Improve TalkBack support#4222
j-piasecki wants to merge 1 commit into
mainfrom
@jpiasecki/improve-talkback-situation

Conversation

@j-piasecki
Copy link
Copy Markdown
Member

Description

Updates touchable components and NativeViewGestureHandler so that TalkBack is better supported:

  • explicitly dispatch LONG_CLICK accessibility action from buttons when defined
  • stop ignoring motion events in NativeViewGestureHandler when TalkBack is enabled
  • don't activate native gesture for button when isPressed is true - this means that events are being passed through to the view, so the default flow should handle it
  • updates Pressable state machine to allow optional states
  • updates Pressable state machine for Android with TalkBack to rely on NATIVE_BEGIN and NATIVE_FINALIZE instead of LONG_PRESS_TOUCHES_DOWN - with TalkBack enabled, the activate action (double tap with button selected) doesn't dispatch events to view; instead, it runs performClick directly - no events on view, no onTouchesDown

Test plan

import React from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import {
  LegacyPressable as LegacyRNGHPressable,
  Pressable as RNGHPressable,
  RectButton,
  Touchable,
} from 'react-native-gesture-handler';

export default function EmptyExample() {
  return (
    <View style={styles.container}>
      <Pressable
        style={{ width: 200, height: 100, backgroundColor: 'lightblue' }}
        onPress={() => {
          console.log('Pressed!');
        }}
        onPressIn={() => {
          console.log('Press In!');
        }}
        onPressOut={() => {
          console.log('Press Out!');
        }}
        onLongPress={() => {
          console.log('Long Press!');
        }}>
        <Text>RN Pressable</Text>
      </Pressable>

      <Touchable
        style={{ width: 200, height: 100, backgroundColor: 'lightgreen' }}
        onPress={() => {
          console.log('Pressed!');
        }}
        onPressIn={() => {
          console.log('Press In!');
        }}
        onPressOut={() => {
          console.log('Press Out!');
        }}
        onLongPress={() => {
          console.log('Long Press!');
        }}>
        <Text>RNGH Touchable</Text>
      </Touchable>

      <Touchable
        style={{ width: 200, height: 100, backgroundColor: 'lightcoral' }}
        accessibilityActions={[
          {
            name: 'activate',
            label: `Activate`,
          },
          {
            name: 'longpress',
            label: `Long Press`,
          },
        ]}
        onAccessibilityAction={(e) => {
          console.log(e.nativeEvent);
        }}>
        <Text>RNGH Touchable with accessibilityActions</Text>
      </Touchable>

      <RNGHPressable
        style={{ width: 200, height: 100, backgroundColor: 'red' }}
        onPress={() => {
          console.log('Pressed!');
        }}
        onPressIn={() => {
          console.log('Press In!');
        }}
        onPressOut={() => {
          console.log('Press Out!');
        }}
        onLongPress={() => {
          console.log('Long Press!');
        }}>
        <Text>RNGH Pressable</Text>
      </RNGHPressable>

      <LegacyRNGHPressable
        style={{ width: 200, height: 100, backgroundColor: 'pink' }}
        onPress={() => {
          console.log('Pressed!');
        }}
        onPressIn={() => {
          console.log('Press In!');
        }}
        onPressOut={() => {
          console.log('Press Out!');
        }}
        onLongPress={() => {
          console.log('Long Press!');
        }}>
        <Text>RNGH Legacy Pressable</Text>
      </LegacyRNGHPressable>

      <RectButton
        style={{ width: 200, height: 100, backgroundColor: 'green' }}
        onPress={() => {
          console.log('Pressed!');
        }}
        onLongPress={() => {
          console.log('Long Press!');
        }}>
        <Text>RNGH RectButton</Text>
      </RectButton>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

Copilot AI review requested due to automatic review settings June 1, 2026 13:54
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves Android TalkBack behavior across RNGH “pressable”/button primitives by ensuring press state transitions and accessibility actions still work even when TalkBack bypasses normal touch-event delivery.

Changes:

  • Adjust Pressable’s Android + screen reader flow to drive onPressIn from NATIVE_BEGIN (with optional LONG_PRESS_TOUCHES_DOWN) and provide a synthetic event payload when needed.
  • Extend the Pressable state machine to support optional steps.
  • Update Android button native code to better support long-press accessibility actions and to avoid activating native handlers when events are already passing through (isPressed case); also stop skipping events in NativeViewGestureHandler under TalkBack.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
packages/react-native-gesture-handler/src/v3/components/Pressable.tsx Sends a synthetic event payload on NATIVE_BEGIN for Android TalkBack so the state machine can call onPressIn.
packages/react-native-gesture-handler/src/components/Pressable/utils.ts Adds viewCenterToPressableEvent helper for synthetic Pressable events.
packages/react-native-gesture-handler/src/components/Pressable/StateMachine.tsx Adds support for optional state-machine steps.
packages/react-native-gesture-handler/src/components/Pressable/stateDefinitions.ts Updates Android accessibility state sequence to rely on NATIVE_BEGIN and make LONG_PRESS_TOUCHES_DOWN optional.
packages/react-native-gesture-handler/src/components/Pressable/Pressable.tsx Mirrors the v3 Pressable TalkBack NATIVE_BEGIN synthetic payload behavior for LegacyPressable.
packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/react/RNGestureHandlerButtonViewManager.kt Adds long-press accessibility handling and refines native-handler activation logic for TalkBack.
packages/react-native-gesture-handler/android/src/main/java/com/swmansion/gesturehandler/core/NativeViewGestureHandler.kt Stops ignoring motion events for RNGH buttons when TalkBack is enabled.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +155 to +159
locationX: centerX,
locationY: centerY,
pageX: centerX,
pageY: centerY,
target: targetId,
Comment on lines +170 to +174
locationX: centerX,
locationY: centerY,
pageX: centerX,
pageY: centerY,
target: targetId,
@@ -879,7 +898,8 @@ class RNGestureHandlerButtonViewManager :
// don't preform click when a child button is pressed (mainly to prevent sound effect of
Copy link
Copy Markdown
Collaborator

@m-bert m-bert left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've looked into the provided example and it seems that Legacy Pressable doesn't work. Maybe it is on my side, but could you double-check that? 😅

Comment on lines +440 to +442
val hasLongPress = hasLongPressAccessibilityAction()
setOnLongClickListener(if (hasLongPress) dummyLongClickListener else null)
isLongClickable = hasLongPress
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can't we just set isLongClickable and then use it?

Suggested change
val hasLongPress = hasLongPressAccessibilityAction()
setOnLongClickListener(if (hasLongPress) dummyLongClickListener else null)
isLongClickable = hasLongPress
isLongClickable = hasLongPressAccessibilityAction()
setOnLongClickListener(if (isLongClickable) dummyLongClickListener else null)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants