facebook / react-native

A framework for building native applications using React
https://reactnative.dev
MIT License
119.19k stars 24.33k forks source link

TouchableOpacity inside ScrollView trigger onPress while scrolling - Android only #27355

Closed vvusts closed 1 year ago

vvusts commented 4 years ago

I recently updated react native from 59.10 to 60.6. I have a View with FlatList each item in list is TouchableOpacity. Until now I was able to scroll list and when stop press item and trigger onPress event. Now while I am scrolling onPress is triggered randomly. It somehow trigger onPress during myScroll. And this is happening only on android devices (But I am testning on v8)

React Native version: System: OS: macOS Mojave 10.14.6 CPU: (4) x64 Intel(R) Core(TM) i5-4258U CPU @ 2.40GHz Memory: 18.89 MB / 8.00 GB Shell: 3.2.57 - /bin/bash Binaries: Node: 10.17.0 - /usr/local/opt/node@10/bin/node Yarn: 1.19.1 - /usr/local/bin/yarn npm: 6.11.3 - /usr/local/opt/node@10/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman SDKs: iOS SDK: Platforms: iOS 13.1, DriverKit 19.0, macOS 10.15, tvOS 13.0, watchOS 6.0 Android SDK: API Levels: 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28 Build Tools: 19.1.0, 20.0.0, 21.1.2, 22.0.1, 23.0.1, 23.0.2, 23.0.3, 24.0.0, 24.0.1, 24.0.2, 24.0.3, 25.0.0, 25.0.2, 25.0.3, 26.0.0, 26.0.1, 26.0.2, 26.0.3, 27.0.3, 28.0.0, 28.0.2, 28.0.3 System Images: android-25 | Google APIs Intel x86 Atom, android-26 | Google APIs Intel x86 Atom, android-27 | Google APIs Intel x86 Atom IDEs: Android Studio: 3.5 AI-191.8026.42.35.5900203 Xcode: 11.1/11A1027 - /usr/bin/xcodebuild npmPackages: react: 16.8.6 => 16.8.6 react-native: 0.60.6 => 0.60.6 npmGlobalPackages: create-react-native-app: 0.0.6 generator-react-native-ignite: 1.13.0 react-native-cli: 2.0.1 react-native-git-upgrade: 0.2.7 react-native-ignite: 1.13.0

Steps To Reproduce

It's just FlatList with TouchableOpacities inside.

Describe what you expected to happen: Not catch onPress event while scroll only if press on item in a list. This was working fine on 59.10

react-native-bot commented 4 years ago

It looks like you are using an older version of React Native. Please update to the latest release, v0.61 and verify if the issue still exists.

The "Resolution: Old Version" label will be removed automatically once you edit your original post with the results of running `react-native info` on a project using the latest release.
cinder92 commented 4 years ago

could you post your code here?

vvusts commented 4 years ago

@cinder92 here you go. I didn't put all code from Touchable as it's a lot of texts inside. As you can see nothing special in code but on Android make real problem. As event is fired during scrolls.

<FlatList
            ref={ref => (this.myList = ref)}
            getItemLayout={(item, index) => ({
              length: 230,
              offset: 230 * index,
              index
            })}
            extraData={this.state}
            showsHorizontalScrollIndicator={false}
            horizontal={true}
            data={[1,2,3,4,5,6,7,8,9,10]}
            renderItem={({ item }) => {
              return (
                  <TouchableOpacity style={{width: 230, height: 230, backgroundColor: '#f00', margin:5}} 
                  onPress={() => 
                       Alert.alert('test')}>
                <Text>{"box"}</Text>         
              </TouchableOpacity>
              )}}
          />

Here you can see video of this: Nov-27-2019 21-09-02

vvusts commented 4 years ago

I just imported TouchableOpacity from import 'react-native-gesture-handler' and this one work fine. I have very large project so I would like not to change this everywhere. Any reason why react-native TouchableOpacity has issue and this one doesn't?

SkariDamm commented 4 years ago

Having the same problem on v0.61.4

ms88privat commented 4 years ago

We noticed it too on a very fresh project setup with 0.61.2.

hcw12889 commented 4 years ago

Same here 0.61.2, was working in 0.58 or below. Happens on Android only.

The TouchableOpacity from react-native-gesture-handler does work but sometimes it ruins the layout of its children. Now it's affecting all the packages using touchable components such as 'react-native-snap-carousel' (Touchable triggered while snapping to the next slide)

Any method to fix this?

hcw12889 commented 4 years ago

After a few days, still haven't found a good solution, seems that the scroll responder of ScrollView/FlatList doesn't properly capture the touch responder (?) so the touchable continues to be the touch responder.

However, instead of using TouchableOpacity from react-native-gesture-handler which ruined the layout of my components, I came up with a very TEMPORARY 'solution', which is to modify the React Native source Touchable.js (node_modules/react-native/Libraries/Components/Touchable/Touchable.js)

The mechanism is to add lines for disabling the original move responder of touchable.

Line 501 (as at RN 0.61.5):

/**
   * Place as callback for a DOM element's `onResponderMove` event.
   */
touchableHandleResponderMove: function(e: PressEvent) {

    // ===== Add the following lines ===== 
    if(Platform.OS=='android' && this.props.removeMoveResponder) {         //android's issue only, so disable the touch move for android
      this._cancelLongPressDelayTimeout();
      this._receiveSignal(Signals.LEAVE_PRESS_RECT, e);
      return;
    }
    // ===== Add the lines above =====

    // Measurement may not have returned yet.
    if (!this.state.touchable.positionOnActivate) {
      return;
    }

....
....

Whenever you place a touchable inside the ScrollView, add a removeMoveResponder prop to it.

<TouchableOpacity onPress={()=>{}}  removeMoveResponder>
    <Text>I do not respond to touch move / scroll</Text>
</TouchableOpacity>

Still finding an elegant solution to it, such as detecting whether the parent of the touchable is a ScrollView / FlatList. It will be much better if there's an official fix, like fixing the touch capturing issue (which should be the way to go but I have no idea how to).

hcw12889 commented 4 years ago

Alright, I found the 'solution' above too buggy in real devices. The onPress action can't be fired as the move gesture is too sensitive to be triggered, so I added a touch move allowance under the 'long press allowance' code.

Accomplished by modifying the React Native source Touchable.js (node_modules/react-native/Libraries/Components/Touchable/Touchable.js)

Line 547 (or sth close): Once the finger moves out, add a scroll flag to make sure the touch won't be fired again when the finger comes back to the original position

if (this.pressInLocation) {
      const movedDistance = this._getDistanceBetweenPoints(
        pageX,
        pageY,
        this.pressInLocation.pageX,
        this.pressInLocation.pageY,
      );
      if (movedDistance > LONG_PRESS_ALLOWED_MOVEMENT) {
        this._cancelLongPressDelayTimeout();
       //======== Add the following line =========
        if(Platform.OS=='android' && this.props.removeMoveResponder) this.scrolledFlag = true;
      }
    }

Line 505 (or sth close, the touchableHandleResponderMove function): If it's determined as scrolled state, then just send a 'moved out of rect' signal.

touchableHandleResponderMove: function(e: PressEvent) {

    //=====Add or change to the following lines=====
    if(this.scrolledFlag) {
      this._cancelLongPressDelayTimeout();
      this._receiveSignal(Signals.LEAVE_PRESS_RECT, e);
      return;
    }
    //=====Add or change to the lines above=====

    // Measurement may not have returned yet.
    if (!this.state.touchable.positionOnActivate) {
      return;
    }

....
.....

Line 488 (touchableHandleResponderRelease function):

touchableHandleResponderRelease: function(e: PressEvent) {
    this.pressInLocation = null;
    this._receiveSignal(Signals.RESPONDER_RELEASE, e);
    this.scrolledFlag = false;             // disable the scroll flag, everything reset
  },

And still, waiting for an official fix (or some fixes suggested by the pros).

annieneedscoffee commented 4 years ago

I'm having the same issue in both Android and IOS using a FlatList with TouchableOpacity and react-native 0.61.5. I also have "react-native-gesture-handler": "^1.5.2" installed in this project, but I'm importing TouchableOpacity from react-native, not from gesture handler.

This solution from stackoverflow seems to be working for me: https://stackoverflow.com/a/37642488/9147743

Here's what I've done in my own code:

<TouchableOpacity delayPressIn={5} delayPressOut={5} delayLongPress={5}

This seems to be a documentation issue. Right now delayPressIn, delayPressOut, and delayLongPress are only listed on the documentation for TouchableWithOutFeedback, and not in the documentation for TouchableOpacity, even though they work for both.

runryan commented 4 years ago

I had the same issue in Android only with React Native 0.61.4. I would not like to change the souce code of React Native. I did the trick by creating a native UI module that generates AndroidButtons for React Native.

public class AndroidButtonManager extends SimpleViewManager<Button> {

    @NonNull @Override public String getName() {
        return "AndroidButton";
    }

    @NonNull @Override
    protected Button createViewInstance(@NonNull final ThemedReactContext reactContext) {
        Activity currentActivity = reactContext.getCurrentActivity();
        Button button = new Button(currentActivity);
        button.setOnClickListener(new View.OnClickListener() {
            @Override public void onClick(View view) {
                WritableMap event = Arguments.createMap();
                reactContext.getJSModule(RCTEventEmitter.class).receiveEvent(view.getId(), "onPress", event);
            }
        });
        return button;
    }

    @Nullable @Override public Map getExportedCustomBubblingEventTypeConstants() {
        return MapBuilder.builder()
                .put( "onPress",  MapBuilder.of( "phasedRegistrationNames",  MapBuilder.of("bubbled", "onPress")))
                .build();
    }
}

Creating AndroidButton in react native:

import * as React from 'react'
import {requireNativeComponent, ViewStyle} from 'react-native'

const RNAndroidButton = requireNativeComponent('AndroidButton')

export interface AndroidButtonProps {
  style?: ViewStyle
  onPress?: () => void
}

class AndroidButton extends React.Component<AndroidButtonProps> {
  constructor(props: AndroidButtonProps) {
    super(props)
    this.state = {}
  }

  public render() {
    const {onPress, style} = this.props
    return (
      <RNAndroidButton
        onPress={onPress}
        style={{width: '100%', height: '100%', position: 'absolute', ...style}}
      />
    )
  }
}

export default AndroidButton

Using AndroidButton in react-native:

 public render() {
    return (
      <View>
       <AndroidButton onPress={() => console.log('did press me')} />
       ...
      </View>
    )
}
tostavio commented 4 years ago

Same problem here. I'm in "react-native": "0.62.2".

I have an FlatList with TouchableOpacity, and the expected behavior is: when I scrolling FlatList, TouchableOpacity onPress don't should be called. His be called only if I press him, not when scrolling his father.

Android

android

IOS

ios

CODE

Just FlatList with an TouchableOpacity as renderItem.

Partial solution:

I have added delayPressIn={100} to my TouchableOpacity. Isn't the better but resolve in parts. Sometimes is possible activate opacity effect with scroll yet, but not always as before. The problem is when I click very fast in TouchableOpacity, the opacity effect don't active before navigation, in my case.

alansoliditydev commented 4 years ago

I have the same problem, too.

fabOnReact commented 4 years ago

I wrote a pull https://github.com/facebook/react-native/pull/28982 to fix a similar issue.

I tested as I'm willing to fix this issue, but I can not reproduce the problem on emulator and real device.

React Native Master branch.

CLICK TO OPEN TESTS RESULTS

| **BEFORE/AFTER** | |:-------------------------:| | |

TQCasey commented 4 years ago

Try this code if you wan to put a TouchableOpacity in a ScrollView / FlatList

import {TouchableWithoutFeedback} from 'react-native-gesture-handler'

class TouchableOpacityEx extends Component {
    constructor (props) {
        super (props);
        this.state = {};

        this.state.opactiy = 1;
    }

    render () {
        return  <TouchableWithoutFeedback
                    onPressIn={() => {
                        // touch began
                        this.setState ({opacity : this.props.activeOpacity || 0.7}) 
                    }}

                    onPressOut={() => {
                        // touch end 
                        this.setState ({opacity : 1}) 
                    }}

                    {...this.props}
                >
                    <View style={[this.props.style,{opacity : this.state.opacity}]}>
                        {this.props.children}
                    </View>
                </TouchableWithoutFeedback>
    }
}

then Just Change TouchableOpacity to TouchableOpacityEx

It works fine for me

TQCasey commented 4 years ago

Same problem here. I'm in "react-native": "0.62.2".

I have an FlatList with TouchableOpacity, and the expected behavior is: when I scrolling FlatList, TouchableOpacity onPress don't should be called. His be called only if I press him, not when scrolling his father.

Android

  • Device: Motorola XT1672 (Moto g5)
  • OS Version: 8.1.0

android

IOS

  • Emulator: Iphone 11
  • OS Version: 13.3

ios

CODE

Just FlatList with an TouchableOpacity as renderItem.

Partial solution:

I have added delayPressIn={100} to my TouchableOpacity. Isn't the better but resolve in parts. Sometimes is possible activate opacity effect with scroll yet, but not always as before. The problem is when I click very fast in TouchableOpacity, the opacity effect don't active before navigation, in my case.

try this code :

import {TouchableWithoutFeedback} from 'react-native-gesture-handler'

class TouchableOpacityEx extends Component {
    constructor (props) {
        super (props);
        this.state = {};

        this.state.opactiy = 1;
    }

    render () {
        return  <TouchableWithoutFeedback
                    onPressIn={() => {
                        // touch began
                        this.setState ({opacity : this.props.activeOpacity || 0.7}) 
                    }}

                    onPressOut={() => {
                        // touch end 
                        this.setState ({opacity : 1}) 
                    }}

                    {...this.props}
                >
                    <View style={[this.props.style,{opacity : this.state.opacity}]}>
                        {this.props.children}
                    </View>
                </TouchableWithoutFeedback>
    }
}
TQCasey commented 4 years ago

I have the same problem, too.

Try this code

import {TouchableWithoutFeedback} from 'react-native-gesture-handler'

class TouchableOpacityEx extends Component {
    constructor (props) {
        super (props);
        this.state = {};

        this.state.opactiy = 1;
    }

    render () {
        return  <TouchableWithoutFeedback
                    onPressIn={() => {
                        // touch began
                        this.setState ({opacity : this.props.activeOpacity || 0.7}) 
                    }}

                    onPressOut={() => {
                        // touch end 
                        this.setState ({opacity : 1}) 
                    }}

                    {...this.props}
                >
                    <View style={[this.props.style,{opacity : this.state.opacity}]}>
                        {this.props.children}
                    </View>
                </TouchableWithoutFeedback>
    }
}
TQCasey commented 4 years ago

I'm having the same issue in both Android and IOS using a FlatList with TouchableOpacity and react-native 0.61.5. I also have "react-native-gesture-handler": "^1.5.2" installed in this project, but I'm importing TouchableOpacity from react-native, not from gesture handler.

This solution from stackoverflow seems to be working for me: https://stackoverflow.com/a/37642488/9147743

Here's what I've done in my own code:

<TouchableOpacity delayPressIn={5} delayPressOut={5} delayLongPress={5}

This seems to be a documentation issue. Right now delayPressIn, delayPressOut, and delayLongPress are only listed on the documentation for TouchableWithOutFeedback, and not in the documentation for TouchableOpacity, even though they work for both.

import {TouchableWithoutFeedback} from 'react-native-gesture-handler'

class TouchableOpacityEx extends Component {
    constructor (props) {
        super (props);
        this.state = {};

        this.state.opactiy = 1;
    }

    render () {
        return  <TouchableWithoutFeedback
                    onPressIn={() => {
                        // touch began
                        this.setState ({opacity : this.props.activeOpacity || 0.7}) 
                    }}

                    onPressOut={() => {
                        // touch end 
                        this.setState ({opacity : 1}) 
                    }}

                    {...this.props}
                >
                    <View style={[this.props.style,{opacity : this.state.opacity}]}>
                        {this.props.children}
                    </View>
                </TouchableWithoutFeedback>
    }
}
djpetenice commented 4 years ago

Any further updates on this? Just updated my app to 0.63.2 and the TouchableOpacity inside a ScrollView are super sensitive. Was working fine on 0.61.2. I've tested with delayPressIn but it just doesn't feel right.

fairySusan commented 4 years ago

so,how to fix this problem? I try all methods, not work for me

dcolonv commented 4 years ago

It worked for me using property onPress instead of onPressIn for both TouchableOpacity and TouchableWithoutFeedback

djpetenice commented 4 years ago

@dcolonv

It worked for me using property onPress instead of onPressIn for both TouchableOpacity and TouchableWithoutFeedback

onPress has always worked - it's just extremely sensitive which is why I've tried the delayPressIn & onPressIn

safaiyeh commented 4 years ago

Pressable was made to support better press events. Let me know if that helps out.

https://reactnative.dev/docs/pressable

carissacks commented 4 years ago

I found the solution here Hope it helps!

I import both TouchableOpacity and ScrollView from 'react-native-gesture-handler', and it works fine! You might see the opacity changes but when I tried to console.log the handler, it doesn't count as 'pressed'

danmaas commented 4 years ago

I was able to fix my issue by applying both of these patches: https://github.com/facebook/react-native/commit/86ffb9c41e033f59599e01b7ad016706b5f62fc8 https://github.com/facebook/react-native/commit/0c392bc4052784de7497bf7b5eaf207b02409877

djpetenice commented 4 years ago

@danmaas just looking at the patches you've applied - I don't have this file in Libraries/Pressability/tests/Pressability-test.js

djpetenice commented 4 years ago

@carissacks this solution worked for me.

danmaas commented 4 years ago

The __tests__ patch is part of react-native's internal test suite, you don't need it.

rendomnet commented 4 years ago

same problem here with RN 0.63.3 and Pressable

huyhai commented 4 years ago

same here

Frans-L commented 3 years ago

This problem also applies to iOS. Tested version with Flatlist and version 0.63.3.

Fortunately, react-native-gesture-handler fixes the problem.

manuelpaulo commented 3 years ago

Scrolling the Facebook main app news feed, on Android, suffers from this issue. Tapping it to drag is not an item select press gesture. Actual behavior creates a constant flicker effect when browsing the feed. Makes one afraid of touching it in order not to trigger it again. Twitter (ReactiveX) doesn't have this issue.

fabOnReact commented 3 years ago

update on my comment https://github.com/facebook/react-native/issues/27355#issuecomment-656727729

in reply to @TQCasey https://github.com/facebook/react-native/issues/27355#issuecomment-659135434

Could you please give us minimum reproducible example of this bug, you code just includes TouchableOpacity, while the issues is caused by TouchableOpacity in a ScrollView. I can try to find a solution to this bug, once I can reproduce it. This is the result of my test. Thanks a lot

CLICK TO OPEN TESTS RESULTS

| **BEFORE/AFTER** | |:-------------------------:| | |

dcx86 commented 3 years ago

I don't understand why but If I move the TouchableOpacity block of code in it's own component and then import it under the scrollView it works just fine.

crazyone33 commented 3 years ago

same problem on react native 0.63,I don't wanna use react-native-gesture-handler, waiting for an official fix, here is my temporary trick( hook the onPress() ):

export default class ClickableView extends React.PureComponent<Props> {
  private pressInLocationX?: number;
  private pressInLocationY?: number;
  private pressOutLocationX?: number;
  private pressOutLocationY?: number;
  public render() {
    const { children, onLayout, activeOpacity, style } = this.props;
    return (
      <TouchableOpacity
        style={[style]}
        activeOpacity={activeOpacity || 0.9}
        onLayout={onLayout}
        onPressIn={(event) => {
          this.pressInLocationX = event.nativeEvent.pageX;
          this.pressInLocationY = event.nativeEvent.pageY;
        }}
        onPressOut={(event) => {
          this.pressOutLocationX = event.nativeEvent.pageX;
          this.pressOutLocationY = event.nativeEvent.pageY;
        }}
        onPress={this.onPress}
        >
        { children }
      </TouchableOpacity>
    );
  }

  private onPress = (event: GestureResponderEvent) => {
    if (this.pressInLocationX && this.pressInLocationY
      && this.pressOutLocationX && this.pressOutLocationY
      && Math.abs(this.pressOutLocationX - this.pressInLocationX) < 3
      && Math.abs(this.pressOutLocationY - this.pressInLocationY) < 3) {
        if (this.props.onClick) {
          this.props.onClick(event);
        }
    }
  }

}
danilhendrasr commented 3 years ago

Any official updates on this? Still exists in RN 0.64.2 Importing touchables and scroll views from react-native-gesture-handler doesn't fix it either.

IsmetGlumcevic commented 3 years ago

I use tocuableOpacity inside flatlist witch is inside rn tab view. I tried with tocuableOpacity from react-native-gesture-handler and then have issue with swiping tabs. For me the solutions from previous comments don’t work. Only don't try native ui button for android.

RN 0.64

IsmetGlumcevic commented 3 years ago

everything works when downgrading react-native-pager-view to v4, rn 0.64, react-native-tab-view 3.1.1, material-top-tabs 6.0.2

fredmanxu commented 3 years ago

同样的问题在这里。我在"react-native": "0.62.2"

我有一个带有 TouchableOpacity 的 FlatList,预期的行为是:当我滚动 FlatList 时,不应调用 TouchableOpacity onPress。只有在我按下他时才会调用他,而不是在滚动他的父亲时调用。

安卓

  • 设备:摩托罗拉XT1672(Moto g5)
  • 操作系统版本:8.1.0

安卓

IOS

  • 模拟器:iPhone 11
  • 操作系统版本:13.3

ios

代码

只需FlatList一个TouchableOpacityas renderItem

部分解决方案:

我已添加delayPressIn={100}到我的TouchableOpacity. 不是更好,而是部分解决。有时可以通过滚动激活不透明效果,但并不总是像以前那样。 问题是当我在 中快速单击时TouchableOpacity,在我的情况下,导航前不透明效果不会激活。

I have the same problem. Have you solved it?

vovkasm commented 3 years ago

Just fixed it... probably (we will test solution in near days)...

This patch chunk (can be automated with patch-package): patches/react-native+0.64.2.patch

diff --git a/node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js b/node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js
index 773e112..7032508 100644
--- a/node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js
+++ b/node_modules/react-native/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js
@@ -1879,7 +1879,7 @@ function canTriggerTransfer(topLevelType, topLevelInst, nativeEvent) {
     topLevelInst && // responderIgnoreScroll: We are trying to migrate away from specifically
     // tracking native scroll events here and responderIgnoreScroll indicates we
     // will send topTouchCancel to handle canceling touch events instead
-    ((topLevelType === TOP_SCROLL && !nativeEvent.responderIgnoreScroll) ||
+    ((topLevelType === TOP_SCROLL) ||
       (trackedTouchCount > 0 && topLevelType === TOP_SELECTION_CHANGE) ||
       isStartish(topLevelType) ||
       isMoveish(topLevelType))

This is for ReactNativeRenderer-dev.js, same should be done for each used renderer (at least for ReactNativeRenderer-prod.js).

Real cause is this condition: https://github.com/facebook/react/blob/v17.0.2/packages/react-native-renderer/src/legacy-events/ResponderEventPlugin.js#L649

Property responderIgnoreScroll are settled unconditionally by native part on Android here: https://github.com/facebook/react-native/blob/0.65-stable/ReactAndroid/src/main/java/com/facebook/react/views/scroll/ScrollEvent.java#L175

I don't understand this comment: https://github.com/facebook/react/blob/v17.0.2/packages/react-native-renderer/src/legacy-events/ResponderEventPlugin.js#L646-L648

responderIgnoreScroll: We are trying to migrate away from specifically tracking native scroll events here and responderIgnoreScroll indicates we will send topTouchCancel to handle canceling touch events instead

Because I cant find any evidence that topTouchCancel is generated. So I can suppose that responderIgnoreScroll should be simple deleted, in that case Android will work exactly as iOS.

fabOnReact commented 3 years ago

can you reproduce this issue with components that are not TouchableOpacity? The change you want to apply changes behavior for all components in ReactNative. If this issue is only in a Specific Component, then I think a change similar to https://github.com/facebook/react-native/pull/29025 for only one component is better than for all components.

responderIgnoreScroll: We are trying to migrate away from specifically tracking native scroll events here and responderIgnoreScroll indicates we will send topTouchCancel to handle canceling touch events instead

https://stackoverflow.com/a/6018458/7295772 https://reactnative.dev/docs/gesture-responder-system#best-practices

vovkasm commented 3 years ago

can you reproduce this issue with components that are not TouchableOpacity?

Yes, we actually not used TouchableOpacity in our app, instead we have custom Touch component with TouchableWithoutFeedback inside (but I testing with Pressable on both 0.64 and 0.65 versions of RN). Originally we use custom scale animations with onPressIn/onPressOut events, but I tested with and without those callbacks. In all cases onPress callback triggered if pointer(tap) inside pressable view area on press-out event even after scroll begins.

Case that was critical for us: FlatList with "infinity" scroll of cards. Each card is big touchable (about 1/3 of screen height). With this bug user almost cant scroll such list, because every gesture ends up with navigation to some card's screen.

The change you want to apply changes behavior for all components in ReactNative. If this issue is only in a Specific Component, then I think a change similar to #29025 for only one component is better than for all components.

My change make ReactNative scroll responder on Android behave exactly as on iOS. It simple make it work consistently across two platforms. And it logically correct: if we begin scroll, we should be "responder" for gesture to the end (if some other "upper" component not steal responder from us).

Distinct question is in what excact commit RN on Android broke "quirk" that exposes that bug, because responderIgnoreScroll property here for all public releases (it is set unconditionally in Android part of RN)?

Ilario17 commented 3 years ago

@vovkasm Hi, have you had the opportunity to better test your solution?

vovkasm commented 3 years ago

@vovkasm Hi, have you had the opportunity to better test your solution?

Yes, it works :-) Our app currently in alpha test with limited set of users though. At least testing team do not found any bugs related to responder change.

vovkasm commented 3 years ago

I'm even thinking about preparing a PR, but I don't know exactly how to do it. Main problem that modified code lives in react repo and I'm not sure who is responsible for changes and how to coordinate changes between react and react-native repos.

vonovak commented 3 years ago

Another solution would be using ScrollView and touchables from RNGH where you could eg. tell the Touchable to waitFor the ScrollView

vovkasm commented 3 years ago

@vonovak Yes, RNGH can be solution/workaround for this, just because it don't use JS responder chain. But the applicability of this solution very much depends on the characteristics of each application.

I can emphasize the following features of using RNGH:

I don’t mean to say that RNGH is bad. On the contrary, it is quite good. And something like it should be in the core of the RN. But now JS responder is in the core of RN, so it's better to fix it.

alencengic commented 3 years ago

If someone else has similar problems you can also try to define onPress like this:

<Pressable
  onPress={(event) => {
    if (event.target == event.currentTarget) {
      Alert.alert('test')}>
    }
  }}>
mohd-sher-khan commented 2 years ago

after spending whole day i found solution for this problem. Do one think import { TouchableWithoutFeedback } from "react-native-gesture-handler";

this will work fine.

fredmanxu commented 2 years ago

I add delayPressIn and delayPressOut to ensure that the underlay color will be triggered every time I click. Use custom variable scrollParams to determine whether the press event should be excuted. My example code.It works great for me.

import React from "react";
import {View, FlatList, TouchableHighlight} from 'react-native'

export default class MovieList extends React.Component {
    scrollParams = {
        isBeginDrag: false,
        dragEndTime: 0
    }

    render() {
        return (
            <FlatList
                data={[1, 2, 3]}
                renderItem={() => this.renderItem()}
                onScrollBeginDrag={() => {
                    this.scrollParams.isBeginDrag = true
                }}
                onMomentumScrollEnd={() => {
                    this.scrollParams.isBeginDrag = false
                    this.scrollParams.dragEndTime = new Date().getTime()
                }}
            />
        )
    }

    renderItem() {
        return (
            <TouchableHighlight
                delayPressIn={100}
                delayPressOut={100}
                underlayColor={'#EEEEEE'}
                onPress={() => {
                    requestAnimationFrame(() => {
                        if (this.scrollParams.isBeginDrag || this.scrollParams.dragEndTime + 100 > new Date().getTime()) {
                            return
                        }
                        //  Do something

                    })
                }}>
                <View>
                </View>
            </TouchableHighlight>
        )
    }
}