React-native: Touchables translated into view are not pressable on android

Created on 14 May 2020  Â·  15Comments  Â·  Source: facebook/react-native

Description

When a touchable is rendered offscreen (or possibly just outside the bounds of its parent) but moved into view using translateX/translateY, it cannot be pressed. This bug is only present on Android, and works fine on iOS.

There's been a few issues related to this that have been closed due to inactivity, so I'm opening a new one. They are:

(@AdamGerthel and @lehresman I'd be interested to know if you got any further debugging these issues?)

React Native version:

0.61.2

Steps To Reproduce

Provide a detailed list of steps that reproduce the issue.

  1. Open the snack https://snack.expo.io/@bdrobinson/android-offscreen-touchable-bug on an android device

Expected Results

When you press the left/right buttons, both should be expected to respond to your touches (you can run on iOS to verify this). However, on Android only the left one responds to touches, whereas the right doesn't.

Snack, code example, screenshot, or link to a repository:

https://snack.expo.io/@bdrobinson/android-offscreen-touchable-bug (copy+pasted from https://github.com/facebook/react-native/issues/26219)

Needs Android

Most helpful comment

Thanks for looking into it so closely @fabriziobertoglio1987! You clearly understand the problem space a lot better than I do so I'm happy to defer to your opinion to make sure that this issue doesn't cause unnecessary noise for maintainers.

However, I would question whether this is indeed an "invalid example" since it works on iOS and fails on Android?

All 15 comments

(@AdamGerthel and @lehresman I'd be interested to know if you got any further debugging these issues?)

I've used workarounds to solve it in the different cases I've had this issue:

@AdamGerthel I am facing a similar issue and wanted to see if you could provide some more clarification. By putting an invisible view behind the touchable surface, do you mean you wrapped the touchable opacity in a view that had no styles?
In my case, I am translating my FAB, however due to the transformations, I can no longer click the opacity. Due to which, I am having to use the top and bottom properties instead. I would have no issue utilizing those properties, it's just that I also can't use native driver without transforming.

@skay97 My code is too obscure for it to make immediate sense and it would take me too much time to rewrite it as a re-usable sample. Basically I first render this view: <View style={{ height: x }} /> where x is the height of the view that is un-clickable on Android. I use onLayout on the Animated View to get the height of the view, and then pass it to the other view (which is placed "behind" the animated one). Exactly how to use these values depend on what you're trying to achieve, which transforms you're using etc.

This line does not take in consideration the transformation effect. It calculates the number of child present on the screen before applying the transformation (https://github.com/facebook/react-native/issues/27333#issuecomment-657566412)

https://github.com/facebook/react-native/blob/0060b5de559cd9c785a2a2a6c66f58088fea4dd2/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java#L114-L115

The number of child i is used to loop around each children of the viewGroup and check if the touch was done inside that View, but as the value childIndex was calculated before the transformation, the button View is not included in this loop

https://github.com/facebook/react-native/blob/0060b5de559cd9c785a2a2a6c66f58088fea4dd2/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java#L119-L122

this is the logic checking that click x, y was performed inside the Button View

https://github.com/facebook/react-native/blob/0060b5de559cd9c785a2a2a6c66f58088fea4dd2/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java#L124-L134

I need to apply the transformation before calculation the number of childrens

https://github.com/facebook/react-native/blob/0060b5de559cd9c785a2a2a6c66f58088fea4dd2/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java#L115

I'll try to prepare a pull request tomorrow

Thanks a lot
I wish you a good day
Fabrizio Bertoglio

https://github.com/facebook/react-native/blob/03489539146556ec5ba6ba07ac338ce200f5b0f4/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java#L320

The transformation is not yet applied and getChildCount will return 1. The loop runs and does not check that onTouchEvent x, y coordinates are in the button area.

https://github.com/facebook/react-native/blob/0060b5de559cd9c785a2a2a6c66f58088fea4dd2/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java#L115-L119

I can detect that the child child has been transformed like this

https://github.com/facebook/react-native/blob/0060b5de559cd9c785a2a2a6c66f58088fea4dd2/ReactAndroid/src/main/java/com/facebook/react/uimanager/TouchTargetHelper.java#L122

        Matrix childMatrix = child.getMatrix();
        Log.w("TESTING::TouchTargetHelper", "childMatrix.isIdentity(): " + childMatrix.isIdentity());
        // Logs false for ViewGroup id 0x17 (23) - a transformation has been applied and not taked in consideration

image

calling setTranslationX and re-executing the process could fix this issue, I'll be testing this afternoon.

https://github.com/facebook/react-native/blob/03489539146556ec5ba6ba07ac338ce200f5b0f4/ReactAndroid/src/main/java/com/facebook/react/uimanager/BaseViewManager.java#L324

another option is triggering with a callback this process after transformation is applied, instead of duplicating the logic

My opinion is that issue https://github.com/facebook/react-native/issues/28894 and https://github.com/facebook/react-native/issues/27333 created by @bdrobinson and @AdamGerthel should be closed.

Both issues do not include acceptable reproducible examples.

@AdamGerthel in https://github.com/facebook/react-native/issues/27333 should have just applied { height: integer } as his container has height = 0. The TouchableOpacity inside a container with height = 0 can not be clicked.

@bdrobinson you can just fix https://github.com/facebook/react-native/issues/28894 by specifying the correct width of the View you want to transform. The View is not clickable because you apply width: Dimensions.get('window').width() and then shift the View 50% to the left. After transform your View Width is 50% of the Screen.

Unluckily same and probably a duplicate of https://github.com/facebook/react-native/issues/27333#issuecomment-657566412

const styles = StyleSheet.create({
  outerContainer: {
  },
  innerContainer: {
    flexDirection: 'row',
    width:1800,
    transform: [{translateX: -200}]
  },
});

If you want the ViewGroup to be clickable with a transform, you need to make sure it has the correct width. I invested a lot of time in both issues to find this mistake, so I would be thankful if you could close both issues.

Thanks
I wish you a good day
Fabrizio Bertoglio

Thanks for looking into it so closely @fabriziobertoglio1987! You clearly understand the problem space a lot better than I do so I'm happy to defer to your opinion to make sure that this issue doesn't cause unnecessary noise for maintainers.

However, I would question whether this is indeed an "invalid example" since it works on iOS and fails on Android?

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as a "Discussion" or add it to the "Backlog" and I will leave it open. Thank you for your contributions.

This is still an issue. Use translateX to position a view outside the viewport -> animate the view into the viewport using translateX -> the view does not receive touch. Touches pass through to the views behind the animated-in view.

One use case is:

  • show a list of items
  • touch an item
  • open a detail view that slides in from the right edge of the screen, with an animation such that the view slides into view instead of just appearing
  • the view does not receive touch on Android

I agree that this is still an issue, and I don't agree with what @fabriziobertoglio1987 is saying either. The fact that a view is interpreted as having height: 0 does not mean that I assigned height: 0 to it (which I clearly haven't). My sample works in both iOS and Web, but not Android, and I'm following very basic CSS principles, which is what is expected when using React Native. The fault lies with RN/Android, not my code.

Basically, my take on it is that the height of the View should be calculated by the native Android code rather than being calculated by hand in JS. Assigning a fixed height prevents dynamic content from being placed in the view. Doing that is what I consider to be a bad workaround for an obvious bug. If position: 'absolute', bottom: 0 causes height: 0 on Android, but not in Web or iOS, then I think the fault lies with RN/Android.

See https://github.com/facebook/react-native/issues/27333#issuecomment-657607542 as well.

I tried all the ideas mentioned above and nothing worked.

...I found replacement touchables from react-native-gesture-handler and it worked right away. Drop-in replacement. Just replace your react-native imports with:

import {
  TouchableNativeFeedback,
  TouchableHighlight,
  TouchableOpacity,
  TouchableWithoutFeedback,
} from 'react-native-gesture-handler';

I get the same issue, I want to make a dropdown,in IOS everything is OK, but android is not work, Who can help me...

I'm experiencing the same issue. It's a real bummer, because I've created a custom horizontal swiper (carousel), and <Pressable>/<TouchableWithoutFeedback> components are not pressable on android at all.
The only workaround I've found so far is by using the TouchableWithoutFeedback export from react-native-gesture-handler since that does not use the JS touch responder system, but I'd feel pretty uncomfortable using that workaround, because I'd have to replace all my app's screens' touchables and I can't use the Pressable API anymore.

@fabriziobertoglio1987 you mentioned you'd prepare a pull request - did you find a native fix for this?

EDIT: Just read the comment about providing valid widths - do you mean the root view should have a width of SCREEN_WIDTH * numberOfHorizontalPages? That's not a good solution. As many users (including me) rely on the removeClippedSubviews property, and that requires views to be offscreen when the parent has overflow: 'hidden', which is redundant if the parent container view is as wide as the whole carousel. (= bad performance)
The parent container should always have a viewport of exactly the phone's dimensions and only the horizontal carousel gets translated on the X axis, so RCTView can optimize offscreen views away. Otherwise those views would have to be rendered, even if they're not visible. Imagine the Snapchat homescreen - you don't want to have all 5 screens rendered at the same time, only the screen(s) that are within the viewport should get rendered.

@mrousavy thanks for the reply

If I still remember well my research explained in https://github.com/facebook/react-native/issues/28894#issuecomment-658262246 made me conclude that ReactAndroid will restrict touch events to parent view area available, so for example if you transform the parent view or set height/width = 0, then you can not touch the TouchableWithoutFeedback

This is included in the ReactNative Touchable docs as I explained in https://github.com/facebook/react-native/issues/27333#issuecomment-657566412

regarding TouchableWithoutFeedback from react-native-gesture-handler, they probably don't use the same Android API included above... so that may be the reason you don't have this limitation..

Probably reading their sourcecode may help us understand how to remove this limitation, but I believe the change will be complex

I may be wrong as I did not work for long time on this issue, once I will have more free time I will have a further look at the issue. Thanks a lot.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

TrakBit picture TrakBit  Â·  3Comments

axelg12 picture axelg12  Â·  3Comments

despairblue picture despairblue  Â·  3Comments

madwed picture madwed  Â·  3Comments

anchetaWern picture anchetaWern  Â·  3Comments