react-native-reanimated
react-native-reanimated copied to clipboard
Android is shaking with useAnimatedScrollHandler
Description
We have a simple Animated FlatList that occupies 30% of the UI initally and lies above a View below. User can drag this list from below all the way up until it occupies 100% of screen. This animation works smothly on iOS, but on Android is quite shaky as user scrolls up slowly.
We tried increasing the input ratio as described here, which helps initially, but then we don't have sync between paddingTop and marginTop anymore.
Reproducible example:
import React from 'react';
import {Dimensions, StyleSheet, Text, View} from 'react-native';
import Animated, {
Extrapolation,
interpolate,
useAnimatedScrollHandler,
useAnimatedStyle,
useSharedValue,
} from 'react-native-reanimated';
const {height: SCREEN_HEIGHT} = Dimensions.get('screen');
const data = Array.from({length: 30}, (_, index) => ({
id: `${index + 1}`,
text: `Item ${index + 1}`,
}));
const App = () => {
const animatedVerticalScroll = useSharedValue(0);
const listAnimatedStyle = useAnimatedStyle(() => {
const marginTopValue = interpolate(
animatedVerticalScroll.value,
[0, SCREEN_HEIGHT * 0.7],
[SCREEN_HEIGHT * 0.7, 0],
Extrapolation.CLAMP,
);
const paddingValue = interpolate(
animatedVerticalScroll.value,
[0, SCREEN_HEIGHT * 0.7],
[0, SCREEN_HEIGHT * 0.7],
Extrapolation.CLAMP,
);
return {
marginTop: marginTopValue,
paddingTop: paddingValue,
};
});
const footerAnimatedStyle = useAnimatedStyle(() => {
const height = interpolate(
animatedVerticalScroll.value,
[0, SCREEN_HEIGHT * 0.7],
[0, SCREEN_HEIGHT * 0.7],
Extrapolation.CLAMP,
);
return {
height: height,
};
});
const scrollHandler = useAnimatedScrollHandler({
onScroll: event => {
animatedVerticalScroll.value = event.contentOffset.y;
},
});
const renderItem = ({item}) => (
<View style={styles.item}>
<Text>{item.text}</Text>
</View>
);
return (
<View style={styles.container}>
<View style={styles.containerBehind}>
<Text>{'Components behind list'}</Text>
</View>
<Animated.FlatList
bounces={false}
data={data}
renderItem={renderItem}
style={[styles.listContainer, listAnimatedStyle]}
onScroll={scrollHandler}
scrollEventThrottle={16}
ListFooterComponent={<Animated.View style={footerAnimatedStyle} />}
/>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
},
containerBehind: {
width: '100%',
height: '100%',
flex: 1,
backgroundColor: 'red',
alignItems: 'center',
justifyContent: 'center',
},
listContainer: {
top: 0,
left: 0,
position: 'absolute',
zIndex: 10,
width: '100%',
height: '100%',
backgroundColor: 'green',
},
item: {
height: 50,
},
});
export default App;
Steps to reproduce
- Build the minimal reproducible example on Android
- Try scrolling green list slowly up
- See shaking/flickering as user is scrolling, iOS is normal.
Snack or a link to a repository
https://github.com/aureosouza/animated-list
Reanimated version
3.3.0
React Native version
0.72.0
Platforms
Android
Workflow
React Native (without Expo)
Acknowledgements
Yes
I'm facing the same issue only on Android
I'm facing same issue, only on Android too, did anyone find a solution for this?
@jvfalco1 I think the ratio solution pointed out on the link suggested by @aureosouza works for android, the higher the ratio value the better it gets.
@aureosouza I think you have a problem here
[0, SCREEN_HEIGHT * 0.7],
[SCREEN_HEIGHT * 0.7, 0],
I may be wrong but I think that's the reason for missing sync between paddingTop and marginTop values, your input range has 3 values and output range has 2 values.
@L0rdCr1s yeah issue seems to be connected with the way Android handles fractional pixel values. We tried doubling the input range, but then we don't have sync between marginTop and paddingTop:
const CUSTOM_HEIGHT = SCREEN_HEIGHT * 0.7;
const listAnimatedStyle = useAnimatedStyle(() => {
const marginTopValue = interpolate(
animatedVerticalScroll.value,
[0, CUSTOM_HEIGHT * 2],
[CUSTOM_HEIGHT, 0],
Extrapolation.CLAMP,
);
const paddingValue = interpolate(
animatedVerticalScroll.value,
[0, CUSTOM_HEIGHT * 2],
[0, CUSTOM_HEIGHT],
Extrapolation.CLAMP,
);
return {
marginTop: marginTopValue,
paddingTop: paddingValue,
};
});
const footerAnimatedStyle = useAnimatedStyle(() => {
const height = interpolate(
animatedVerticalScroll.value,
[0, CUSTOM_HEIGHT * 2],
[0, CUSTOM_HEIGHT],
Extrapolation.CLAMP,
);
return {
height: height,
};
});
If Android has a different behaviour with the fractional pixel values as described here, could react-native-reanimated
adjust the interpolation function to avoid these issues on Android?
One of the explanations I found was that the offset given by iOS is a series like 1, 1.5, 2, 2.5 but android gives offset in decimal values.
@piaskowyk are you able to reproduce this scenario on the provided repo example? Any ideas on how to solve this shaking for Android? iOS works well, thanks
Is anyone else is experiencing weird scrollY.value
behavior in FlashList
or FlatList
on Android in react-native 0.72.x
?
This piece of code worked as a charm on < 0.72
but in 0.72
the scrollY.value
won't reach 0
on Android emulator and real device if user is not scrolling on top fast enough.
const tabsAnimatedStyle = useAnimatedStyle(() => {
const top = interpolate(
scrollY.value,
[0, topMargin + 54],
[
Platform.OS === "ios" ? topMargin + 54 : 46,
Platform.OS === "ios" ? topMargin + 8 : 0
],
Extrapolation.CLAMP
);
return {
top
};
});
video of the issue from the repo provided on first comment, the flicker is not the only issue, notice that when I try to scroll on top of the list on Android the gesture is stuck and I have to swipe down multiple times to get on top of the list.
https://github.com/software-mansion/react-native-reanimated/assets/717975/290ea93b-b843-43c9-baec-d1bf273cf76a
@tjzel any ideas on fix for this case? Android seems to have a specific behaviour when comparing to iOS for interpolation, as pointed as well by @efstathiosntonas
@tomekzaw Hi, is there any progress in this issue? It's a blocking for us, we cannot release app update with 0.72 because of this. Thanks
possibly related: https://github.com/software-mansion/react-native-reanimated/issues/4626
Hi, I'm currently investigating this issue. I will keep you updated as soon as I learn about the causes. I can already give a few answers here:
-
scrollEventThrottle
property is only available on iOS (but it's still kind-of irrelevant here - Android tries to send the event once per frame) -
interpolate
function is not the issue here. First of all, it's use here is a bit of an overkill (but is still valid). When I swapped it everywhere to a simple clamped linear function e.g.:
- const height = interpolate(
- animatedVerticalScroll.value,
- [0, SCREEN_HEIGHT * 0.7],
- [0, SCREEN_HEIGHT * 0.7],
- Extrapolation.CLAMP
- );
+ const height =
+ animatedVerticalScroll.value > SCREEN_HEIGHT * 0.7
+ ? SCREEN_HEIGHT * 0.7
+ : animatedVerticalScroll.value;
The issue persists.
- After trying different options I narrowed down that it's
marginTop
property that is causing weird flickering - if you pass sharedValue to it in this setup. Now I'm trying to figure out why.
@tjzel any ideas why the list fails to reach the top when user is swiping down? It's happening in the repro but in my case I animate 3 elements, if I make one element static then it works as a charm. It feels like it fails to synchronize all 4 things:
- the 3 elements
- the list
Can't figure out why it works after making one of the elements static. Please note that my code was working perfectly on rn < 0.72
. I've gone through the release notes of rn 0.72.x
but I cannot find something that could have caused this. I'm not quite sure tho if going through the commits on release notes page is enough since there might be internal commits that never reach the release changelog 🤷
thanks for looking into this.
As you may noticed I opened an issue about this on react-native
GitHub since I narrowed it down to be stemming only from react-native
- after I rewrote your repro to not use react-native-reanimated
.
@efstathiosntonas videos you posted seem to be have the same cause as the reported jitter but I'm not sure what are you talking about when you mention those 3 elements. I suggest you try to rewrite your issue to not use react-native-reanimated
and see if there is still the same (although maybe a bit more laggy) behavior on both platforms. If yes, then it's most likely not because of reanimated. If not, feel free to open a new issue so we don't have those thing mixed up all in one issue.
@tjzel thanks for looking into it. I ll try to create a repro tomorrow to see it in action. What I mean by 3 elements is that the list is inside react-native-tab-view
with a row of buttons above and a horizontal list just below the tabs.
- ——Buttons———
- tab 1 - tab 2 - tab 3
- ——horizontal list——-
- Flash list
as user is scrolling down the list all elements are moving upwards with a fade out and only the element no.2 stays on top of the screen. If I make that (no.2) element static ie. not moving with the other two then I’m able to scroll on top without “stopping” the list.
@tjzel see the attached videos, I know they don't help much, just to have a clear view, please note that this worked as a charm on 0.71.12
reanimated 3.3.0
All elements animated
https://github.com/software-mansion/react-native-reanimated/assets/717975/73f289b6-8cc5-4149-9fcd-0d860aafc32f
Tab bar is static, no animations attached at all
https://github.com/software-mansion/react-native-reanimated/assets/717975/dec4ff6e-0906-43f0-8ae6-2878d1dc51c1
@tjzel I've rolled back to rn 0.71.11
and rean 3.3.0
and I've logged the event of useAnimatedScrollHandler
and I've noticed that the eventName starts with a number:
{
"contentInset" : {
"bottom": 0,
"left" : 0,
"right" : 0,
"top" : 0
},
"contentOffset" : {
"x": 0,
"y": 0
},
"contentSize" : {
"height": 7487.33349609375,
"width" : 360
},
"eventName" : "12359onScroll",
"layoutMeasurement" : {
"height": 586.6666870117188,
"width" : 360
},
"responderIgnoreScroll": true,
"target" : 12359,
"velocity" : {
"x": 0,
"y": -9.666666984558105
}
}
the target: 12359
is prefixing the eventName: 12359onScroll
Did the same with react-native 0.72.3
with rean nightly and the eventName is always onScroll
while scrolling the list and the freeze is appearing while scrolling on top fast, on 0.71.11
is smooth as butter.
0.72.3
event:
{
"contentInset" : {
"bottom": 0,
"left" : 0,
"right" : 0,
"top" : 0
},
"contentOffset" : {
"x": 0,
"y": 966.6666870117188
},
"contentSize" : {
"height": 10778,
"width" : 360
},
"eventName" : "onScroll",
"layoutMeasurement" : {
"height": 615,
"width" : 360
},
"responderIgnoreScroll": true,
"target" : 7735,
"velocity" : {
"x": 0,
"y": 0.03999999910593033
}
}
Please note that I'm using FlashList
.
Don't know if this helps or not but it's really weird.
Sorry for the late reply about that but in regard to event names, yes, it's correct. We are adding a timestamp on the beginning of event name since this PR got merged.
@tjzel I’ve managed to fix the animation freeze, I was animating the absolute position top
and it turns out it was a matter of number calculations. It’s weird because it was smooth on rn < 0.72.
When I animated the translateY
it worked as a charm with the problematic numbers but then I encountered issues with the whole list shifting up leaving space at the bottom.
tldr; the issue must be when animating absolute position values eg. top, interpolating translateY with the same (problematic) numbers/values it works as expected but leads to other ui issues.
@efstathiosntonas I think it's more about layout and non-layout prop, transform
is a non-layout props, meaning, changing component's transform
property does not affect other components layout (position), they do not have to adjust. However top
is a layout prop, meaning if a component gets its top
changed all the other components have to recalculate their position as well.
got the same issue, did you find a solution other than increasing input range? @aureosouza
I found the content offset sent from android scroll view could sometimes go out of order, which causes the shake.
I'm facing the same issue only on Android too, any solution yet?
Shaking appears to occur when the height or coordinate value of the AnimatedFlatList itself changes while scrolling down the AnimatedFlatList
stopgap measure:
header height:200 (position:"absolute")
Flatlist height: 800 (ListHeaderComponent : empty Animated.View height 200)
scroll down
header height: 100, ListHeaderComponent height 100
no shaking
i am facing same issue any only on android . any resolutio found yet?
Is there any update about this isse @tjzel
@guvenkaranfil I bumped the issue in React Native's repo but it doesn't seem to have a high priority at the moment. I can try to look into it a bit more and see what's the cause of the problem in React Native but I cannot guarantee how soon would that be.
I am facing same issue .
Hey @tjzel -- any update on this?
Unfortunately, no updates. I've been planning to maybe try and fix it in React Native myself but it's sadly pretty far on the priority list - but I haven't forgotten about it!
did make it work with adjusting the input range, in my case was the input range [100 , 0], and changed it to the following. Note: i am using useAnimatedStyle
height: interpolate(
scrollY.value,
[130, 0],
[0, heightFontRatio],
Extrapolation.CLAMP,
)