-
-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[Android][New Arch] Buttons inside Animated.View with translate are not clickable #6676
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
This comment has been minimized.
This comment has been minimized.
I had a similar problem. To fix it I had to use the |
I have a similar issue, basically I use 'react-native-popover-view' to list a menu with TouchableOpacity from react elements. When I try to press them after showing the popup which uses Animated.View elements inside (pure TypeScript, no native stuff) they are not pressable at all. |
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
@j-piasecki reported another issue with root cause explanation: #6832 |
Should be fixed with #7014. |
…ePropsOnUIThread` (#7014) ## Motivation Currently, there are two ways to update native view props and styles in Reanimated. The default path (so-called slow path) is to apply all props changes to the ShadowTree via C++ API and let React Native mount the changes. However, if all props updated in given batch are non-layout props (i.e. those that don't require layout recalculation, like background color or opacity) we use a fast path that calls `synchronouslyUpdatePropsOnUIThread` from React Native and applies the changes directly to platform views, without making changes to ShadowTree in C++. Turns out, some features like view measurement or touch detection system use C++ ShadowTree which is not consistent with what's currently on the screen. Because of that, we're removing the fast path (turns out it's not that fast, especially on iOS) to restore the correctness of view measurement and touch detection for animated components. ## Benchmarks * Performance monitor example → Bokeh Example * Android emulator / iPhone 14 Pro real device * Debug mode * Animating `transform` prop using `useAnimatedStyle` | Platform | Before (main) | After (this PR) | |:-:|:-:|:-:| | Android (count=200) | 20 fps | 15 fps | | iOS (count=500) | 22 fps | 22 fps | <details> <summary>App.tsx</summary> ```tsx import React, { useState } from 'react'; import { Dimensions, StyleSheet, View } from 'react-native'; import Animated, { Easing, useAnimatedStyle, useReducedMotion, useSharedValue, withTiming, } from 'react-native-reanimated'; const dimensions = Dimensions.get('window'); function randBetween(min: number, max: number) { return min + Math.random() * (max - min); } function Circle() { const shouldReduceMotion = useReducedMotion(); const [power] = useState(randBetween(0, 1)); const [duration] = useState(randBetween(2000, 3000)); const size = 100 + power * 250; const width = size; const height = size; const hue = randBetween(100, 200); const backgroundColor = `hsl(${hue},100%,50%)`; const opacity = 0.1 + (1 - power) * 0.1; const config = { duration, easing: Easing.linear }; const left = useSharedValue(randBetween(0, dimensions.width) - size / 2); const top = useSharedValue(randBetween(0, dimensions.height) - size / 2); const update = () => { left.value = withTiming(left.value + randBetween(-100, 100), config); top.value = withTiming(top.value + randBetween(-100, 100), config); }; React.useEffect(() => { update(); if (shouldReduceMotion) { return; } const id = setInterval(update, duration); return () => clearInterval(id); }); const animatedStyle = useAnimatedStyle( () => ({ transform: [{ translateX: left.value }, { translateY: top.value }], }), [] ); return ( <Animated.View style={[ styles.circle, { width, height, backgroundColor, opacity }, animatedStyle, ]} /> ); } interface BokehProps { count: number; } function Bokeh({ count }: BokehProps) { return ( <> {[...Array(count)].map((_, i) => ( <Circle key={i} /> ))} </> ); } export default function App() { return ( <View style={styles.container}> <Bokeh count={200} /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: 'black', overflow: 'hidden', }, circle: { position: 'absolute', borderRadius: 999, }, }); ``` </details> ## Summary * Fixes #6832. * Fixes #6676. ## Test plan
Thanks for the fix @tomekzaw & @j-piasecki 🙏 Do you have an idea on when it will be released? I have issues with KeyboardStickyView of react-native-keyboard-controller - Text input inside becomes un-clickable on Android (sometimes). As the |
@delphinebugner We don't have an official timeline for beta.3 yet but you can always try a nightly build, e.g. As for |
…ePropsOnUIThread` (#7014) Currently, there are two ways to update native view props and styles in Reanimated. The default path (so-called slow path) is to apply all props changes to the ShadowTree via C++ API and let React Native mount the changes. However, if all props updated in given batch are non-layout props (i.e. those that don't require layout recalculation, like background color or opacity) we use a fast path that calls `synchronouslyUpdatePropsOnUIThread` from React Native and applies the changes directly to platform views, without making changes to ShadowTree in C++. Turns out, some features like view measurement or touch detection system use C++ ShadowTree which is not consistent with what's currently on the screen. Because of that, we're removing the fast path (turns out it's not that fast, especially on iOS) to restore the correctness of view measurement and touch detection for animated components. * Performance monitor example → Bokeh Example * Android emulator / iPhone 14 Pro real device * Debug mode * Animating `transform` prop using `useAnimatedStyle` | Platform | Before (main) | After (this PR) | |:-:|:-:|:-:| | Android (count=200) | 20 fps | 15 fps | | iOS (count=500) | 22 fps | 22 fps | <details> <summary>App.tsx</summary> ```tsx import React, { useState } from 'react'; import { Dimensions, StyleSheet, View } from 'react-native'; import Animated, { Easing, useAnimatedStyle, useReducedMotion, useSharedValue, withTiming, } from 'react-native-reanimated'; const dimensions = Dimensions.get('window'); function randBetween(min: number, max: number) { return min + Math.random() * (max - min); } function Circle() { const shouldReduceMotion = useReducedMotion(); const [power] = useState(randBetween(0, 1)); const [duration] = useState(randBetween(2000, 3000)); const size = 100 + power * 250; const width = size; const height = size; const hue = randBetween(100, 200); const backgroundColor = `hsl(${hue},100%,50%)`; const opacity = 0.1 + (1 - power) * 0.1; const config = { duration, easing: Easing.linear }; const left = useSharedValue(randBetween(0, dimensions.width) - size / 2); const top = useSharedValue(randBetween(0, dimensions.height) - size / 2); const update = () => { left.value = withTiming(left.value + randBetween(-100, 100), config); top.value = withTiming(top.value + randBetween(-100, 100), config); }; React.useEffect(() => { update(); if (shouldReduceMotion) { return; } const id = setInterval(update, duration); return () => clearInterval(id); }); const animatedStyle = useAnimatedStyle( () => ({ transform: [{ translateX: left.value }, { translateY: top.value }], }), [] ); return ( <Animated.View style={[ styles.circle, { width, height, backgroundColor, opacity }, animatedStyle, ]} /> ); } interface BokehProps { count: number; } function Bokeh({ count }: BokehProps) { return ( <> {[...Array(count)].map((_, i) => ( <Circle key={i} /> ))} </> ); } export default function App() { return ( <View style={styles.container}> <Bokeh count={200} /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: 'black', overflow: 'hidden', }, circle: { position: 'absolute', borderRadius: 999, }, }); ``` </details> * Fixes #6832. * Fixes #6676.
…ePropsOnUIThread` (#7014) Currently, there are two ways to update native view props and styles in Reanimated. The default path (so-called slow path) is to apply all props changes to the ShadowTree via C++ API and let React Native mount the changes. However, if all props updated in given batch are non-layout props (i.e. those that don't require layout recalculation, like background color or opacity) we use a fast path that calls `synchronouslyUpdatePropsOnUIThread` from React Native and applies the changes directly to platform views, without making changes to ShadowTree in C++. Turns out, some features like view measurement or touch detection system use C++ ShadowTree which is not consistent with what's currently on the screen. Because of that, we're removing the fast path (turns out it's not that fast, especially on iOS) to restore the correctness of view measurement and touch detection for animated components. * Performance monitor example → Bokeh Example * Android emulator / iPhone 14 Pro real device * Debug mode * Animating `transform` prop using `useAnimatedStyle` | Platform | Before (main) | After (this PR) | |:-:|:-:|:-:| | Android (count=200) | 20 fps | 15 fps | | iOS (count=500) | 22 fps | 22 fps | <details> <summary>App.tsx</summary> ```tsx import React, { useState } from 'react'; import { Dimensions, StyleSheet, View } from 'react-native'; import Animated, { Easing, useAnimatedStyle, useReducedMotion, useSharedValue, withTiming, } from 'react-native-reanimated'; const dimensions = Dimensions.get('window'); function randBetween(min: number, max: number) { return min + Math.random() * (max - min); } function Circle() { const shouldReduceMotion = useReducedMotion(); const [power] = useState(randBetween(0, 1)); const [duration] = useState(randBetween(2000, 3000)); const size = 100 + power * 250; const width = size; const height = size; const hue = randBetween(100, 200); const backgroundColor = `hsl(${hue},100%,50%)`; const opacity = 0.1 + (1 - power) * 0.1; const config = { duration, easing: Easing.linear }; const left = useSharedValue(randBetween(0, dimensions.width) - size / 2); const top = useSharedValue(randBetween(0, dimensions.height) - size / 2); const update = () => { left.value = withTiming(left.value + randBetween(-100, 100), config); top.value = withTiming(top.value + randBetween(-100, 100), config); }; React.useEffect(() => { update(); if (shouldReduceMotion) { return; } const id = setInterval(update, duration); return () => clearInterval(id); }); const animatedStyle = useAnimatedStyle( () => ({ transform: [{ translateX: left.value }, { translateY: top.value }], }), [] ); return ( <Animated.View style={[ styles.circle, { width, height, backgroundColor, opacity }, animatedStyle, ]} /> ); } interface BokehProps { count: number; } function Bokeh({ count }: BokehProps) { return ( <> {[...Array(count)].map((_, i) => ( <Circle key={i} /> ))} </> ); } export default function App() { return ( <View style={styles.container}> <Bokeh count={200} /> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', backgroundColor: 'black', overflow: 'hidden', }, circle: { position: 'absolute', borderRadius: 999, }, }); ``` </details> * Fixes #6832. * Fixes #6676.
would love to see this patch applied to v3. Having a beta dependencies in our app freak me out. I'll do a .patch but as it's cpp code I'm unsure it is a good idea. |
@HugoGresse This patch has been already backported to v3 (in 3.17.2). Please try upgrading to the latest v3 release which currently is |
Thank you Tomasz for the quick answer! |
@HugoGresse Okay, so this sounds like another issue, this time it's an issue in React Native itself. Can you please try the workaround described in this comment and let me know if it helps? #6935 (comment) |
sorry for my last comment, the issue persist. The touch is visually visible (ripple or over effect) but the onPress is never fired, only when the transform translationY is zero |
Same here, we upgraded to RN 0.79.2, enabled those flags but this does not fix the issue on Android. We use |
Description
On my app, I have buttons inside Animated Views that translate up.
Right after the translation finished, the pressable action of the button is not executed on click.
If we execute another action (ex. navigating on an other tab and come back), the pressable action is executed on click.
This is what we see on the repro below:
Additional notes:
onPress
callback is'nt triggeredCode
Code looks like this:
A full example can be found here: https://github.com/delphinebugner/repro
Further notes
Thanks for working on the lib 🙂
If the problem is linked to a messy Reanimated usage in the component, happy to correct my code!
Steps to reproduce
On the repro application:
Snack or a link to a repository
https://github.com/delphinebugner/repro
Reanimated version
3.16.1
React Native version
0.75.0
Platforms
Android
JavaScript runtime
Hermes
Workflow
Expo Dev Client
Architecture
Fabric (New Architecture)
Build type
Debug app & dev bundle
Device
Real device
Device model
Samsung Galaxy S22
Acknowledgements
Yes
The text was updated successfully, but these errors were encountered: