react native animation : screen shaking when scrolling slowing - react-native

I am using Animated.View to change the header height.
It works well in ios but in android, when I scroll slowly the entire view is shaking.
1) First I set the state
this.state = {
scrollY:new Animated.Value(0)
}
2) Inside the render() I render the height of the view I want to animate.
const HeaderHeight = this.state.scrollY.interpolate({
inputRange: [0, 100],
outputRange: [100, 0],
extrapolate: 'clamp'
})
3) I set my Header like this:
<Animated.View style={{width:'100%', height:HeaderHeight, backgroundColor:'transparent', justifyContent:'flex-end'}}>
...
</Animated.View>
4) Inside the scrollview:
<ScrollView
scrollEventThrottle={16}
onScroll={Animated.event([{ nativeEvent: { contentOffset: { y: this.state.scrollY } } }])}
>
As you could see from the gif file when I slowly scroll the view the screen is shaking. This is happening in android. On ios it works fine.
Any idea how to fix this?
Any comments or advice would be really helpful :)

Your inputRange [0,100] and outputRange[100,0] have a ratio of 1.
This means that for each pixel you move in the ScrollView, your HeaderHeight will be reduced by one, which sounds great but the value you get from the scrollview event is not an integer, is a double and based on those tiny digits it will try to "aspect ratio" your outputRange and this is quite sensitive in Android and therefore the shaking.
Increase your inputRange to [0, 200] so it has a ratio of 2 related to the outputRange. This will remove the shaking.

It can be a little tricky but try to avoid set translate prop to FlatList`s style, or component that wrap Flatlist, jumps will disappear. For adding empty space before first item you can set contentContainer={{paddingTop:}}. It was in my case, hope this help someone.

Make your scrollview animated.
<Animated.ScrollView
style={{flex: 1}}
onScroll={Animated.event([
{ nativeEvent: { contentOffset: { y: this.scrollY } } }
])}
>
...
...
</Animated.ScrollView>

Related

React Native: Is there a way to make components scrollable/sticky without using ScrollView?

New to react native. Here is what I am trying to do:
Render a page with three components: top panel, middle row, and content box.
When the user scrolls down, the top panel is scrolled and disappears, middle row is scrolled but is sticky to the top of the screen, and the content box is scrolled all the way down until the end of the content.
Below code serves my intention. However, when I use this code, I get warnings about nesting virtualized views.
return (
<View style={style.profileContainer}>
<ScrollView
stickyHeaderIndices={[1]}
showsVerticalScrollIndicator={false}
>
<TopPanel /> // This is a view component and is scrolled up as the user scrolls down.
<MiddleRow /> // This is a view component that is sticky to the top of the screen
<BottomArray /> //This is a FlatList
</ScrollView>
</View>
)
Below code gets rid of the warning but all the scroll/sticky behaviors of the top/middle components disappear. They just remain fixed as the user scrolls down.
return (
<View style={style.profileContainer}>
<SafeAreaView style={{flex:1}}>
<TopPanel /> // This is a view component and is fixed as the user scrolls down.
<MiddleRow /> // This is a view component and is fixed as the user scrolls down.
<BottomArray /> //This is a FlatList
</SafeAreaView>
</View>
)
Is there a way to make the top panel scrollable and middle row sticky without relying on ScrollView? This is one of the key interfaces of the app and I'd like to keep it alive.
Thanks!
I think if you put the Middle Row inside a Scroll View, that will help. I am new to react-native, so I am pretty sure this won't help you a lot and I think you would have already tried it.
If i understand correctly what you are looking to achieve, you can wrap the 3 boxes inside a view, have the top and middle inside a view and the content inside a scrollview:
<View>
<View><Text>TOP SECTION</Text></View>
<View><Text>Sticky section</Text></View>
<ScrollView><Text>...Content</Text></ScrollView>
</View>
then using animate you can animate the size (for the top section) and the position (of the sticky section) based on the scroll position of the content section.
first you define you scrollY as a ref:
const scrollY= useRef(new Animated.Value(0)).current;
then you convert the views to Animated and "link" the scrollY ref to the scrolling of the scrollview:
<View>
<Animated.View><Text>TOP SECTION</Text></Animated.View>
<Animated.View><Text>Sticky section</Text></Animated.View>
<Animated.ScrollView
scrollEventThrottle={16}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: scrollY } } }], // attach the scroll to scrollY
{ useNativeDriver: true },
)}>
>
<Text>...Content</Text>
</Animated.ScrollView>
</View>
at this point you can define the function that handle position, size, opacity and so on based on the scrollY position, assuming you know the header height, this function interpolate (map the values) the scroll position of the scrollview from 0 to the height of the header, giving an innverse output range (from 0 to -header heigth):
const headerTranslateY = scrollY.interpolate({
inputRange: [0, HEADER_HEIGHT],
outputRange: [0, -HEADER_HEIGHT],
extrapolate: 'clamp',
});
this function can be attached to the trasform property of the style of the view that you want to animate:
<Animated.View styles={[styles.customstyle, {transform: [{ translateY: headerTranslateY }]}]><Text>TOP SECTION</Text></Animated.View>
basically with interpolate you constatly set values of something, based (in this case) of the scrolling offset of your scrollview (you can read more here on interpolation https://reactnative.dev/docs/animations#interpolation)

React Native animate view position based on flat list offset

I want to animate a view's(say abcView) position based on main container flat list scroll offset. If the user scrolls down, abcView's position should move to bottom proportionately(vice versa when user scrolls back up). The position of abcView should change in proportion to the flatlist offset and not in one go. I want to use animated API but need ideas how to proceed with this.
You could try below functional component
function SomeComponent() {
const [scrollY] = useState(new Animated.Value(0));
const translateY = scrollY.interpolate({
inputRange: [0, 150],
outputRange: [0, 150],
extrapolate: 'clamp',
useNativeDriver: true,
});
return (
<View>
<Animated.FlatList
onScroll={Animated.event([{ nativeEvent: { contentOffset: { y: scrollY } } }])}
/>
<Animated.View
style={{
transform: [{ translateY: translateY }],
}}
></Animated.View>
</View>
)
}
Since you said you need ideas, I think what you are looking for is the parallax effect. You can either go ahead building your own or you can search for such libraries e.g. react-native-parallax-header

How to round button before rendering?

I want to create a button component that will automatically have rounded corners, no matter its dimension.
As you know, to achieve rounded corners, one way to achieve it is to specify the border radius as half of the height of the button.
The way I implemented is that in the custom component I use the onLayout function like this:
onLayout(event: LayoutChangeEvent) {
const { height } = event.nativeEvent.layout;
this.setState({ borderRadius: height / 2 });
}
The problem is that the button will initially appear on screen as a rectangle and only after a millisecond, it will round the corners causing a flicker.
My guess is that onLayout is called after the component renders.
How would one go about implementing this? Thanks!
Before the borderRadius is calculated, you could return transparent button, this would prevent this flickering effect...
// you pass radius, and height from component state
const MyButton = ({ radius, height }) => {
if (radius === null) return <View style={{ backgroundColor: transparent }}>...</View>
else return <View style={{ borderRadius: radius, backgroundColor: 'red' }}>...</View>;
};
To do this precisely, you would need to know the size that the string would take up once rendered. I wasn't able to find a React Native API for this (and I'm assuming you couldn't either), but I know both Android and iOS have such APIs. So therefore the solution would be to create a native module (https://facebook.github.io/react-native/docs/native-modules-android.html) for in iOS and Android which exposes a method called "measureText" or something. Then in each native class you'd use the corresponding API:
Android API mentioned in this answer should work: https://stackoverflow.com/a/7329169/3930970
iOS API: https://developer.apple.com/documentation/foundation/nsstring/1531844-sizewithattributes
I haven't actually tried this so I'm curious if something like this ends up working. Cheers!
You can use the lifecycle method componentWillMount() to calculate the border radius:
componentWillMount() {
const { height } = Dimensions.get('window');
radius = height / 2;
}
This method is only called one time, which is before the initial
render. Since this method is called before render().
And then, you can style your button using the calculated radius:
<TouchableOpacity
style={[styles.button, { borderRadius: radius }]}
onPress={() => alert('Hello World!') }
>
Here is a working demo.

Animated header on scrollview

I have a scrollview on top of which i display a header. My goal is to animated so that when the user scrolls down, the header collapse and when the view scrolls up, the header expands.
I found plenty of exemple where the user has to scroll all the way up for the header to expand. What i'd like is for the header to expand as soon as the scroll view scrolls up.
How can I achieve this?
Here is what I have so far:
type State = { scrollY: Animated.Value };
....
headerHeight = this.state.scrollY.interpolate({
inputRange: [
0, 60
],
outputRange: [60, 0],
extrapolate: "clamp"
});
<Animated.View style={{ height: headerHeight, backgroundColor: "#F0f" }}
>
<ScreenHeader
ref={this.screenHeaderRef}
onTouchAvatar={this.handleOpenProfile}
onTouchNotifications={this.handleOpenNotification}
user={currentUser}
newNotifications={this.props.newNotifications}
/>
</Animated.View>
<WrappedComponent
onScroll={Animated.event([
{
nativeEvent: {
contentOffset: {
y:
this.state.scrollY
}
}
}
])}
onMomentumScrollEnd={this.handleMomentumScrollEnd}
{...this.props}
/>
So you are right about the animating the translateY property. So I did it a bit different way for sake of this question (Poorly, sorry about that, you need to play around a bit with this to get the right animation).
So try to set the style of the animated view's translateY to 0;
<Animated.View
style={[{
transform: [
{
translateY: this.transform
}
]
}, {
backgroundColor: 'red'
}]}
>
Then on scroll set it to -100 or whatever your header height.
later when the user comes back up, towards the top 0, you set it up to 0.
This will do the trick, hopefully.
Here's a small example, sorry for getting back late. :)
https://snack.expo.io/#subkundu/header-hideshow

Animating Height when scrolling a ScrollView

So im trying to animate a view by changing the view's height when a user scrolls a specific scrollview. It looks fine on IOS although it has some jitter going on but the flickering is very visible on android. Here is my code.
Scrollview's on scroll
onScroll={(e) => {
this.state.profileTabAnimatedValue.setValue(e.nativeEvent.contentOffset.y);
}}
height interpolate object
const headerHeight = this.state.profileTabAnimatedValue.interpolate({
inputRange: [0,1],
outputRange: [1,100]
});
I will then apply the headerHeight to a view to adjust it's height
Try using the Animated event method:
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {y: this.state.profileTabAnimatedValue}}}]
)}
Also, if you don't want some weird behaviours like negative height or so, add this in your interpolation:
const headerHeight = this.state.profileTabAnimatedValue.interpolate({
inputRange: [0,1],
outputRange: [1,100]
extrapolate: 'clamp'
});
Last thing is you can play a bit with throttling your events firing on your ScrollView with scrollEventThrottle, just read this