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
Related
I a trying to build a 'Material-UI-like' TextInput with a large label that shrinks down when the field is selected.
I am having issues with scaling the label. Applying a transform: [{scale: ...}] shrinks the Text, but does so around the center of the field. I am failing to keep the label left-aligned during the scaling process, as I'd need to dynamically be able to access the view's width to offset it, but I can't seem to be able to get it using normal means(i.e. onLayout, which does not seem to be triggered during the animation).
Here is an example demonstrating the issue:
import React from 'react';
import { View, Text, TextInput, Animated } from 'react-native';
export const F = (): JSX.Element => {
const scale = React.useRef(new Animated.Value(0.0)).current;
React.useEffect(() => {
const animation = Animated.timing(scale, {
toValue: 1.0,
duration: 1000,
useNativeDriver: true,
});
animation.start();
}, []);
return (
<View style={{ backgroundColor: 'red' }}>
<Animated.View
style={{
transform: [
{
scale: scale.interpolate({
inputRange: [0, 1],
outputRange: [1, 0.5],
}),
},
],
backgroundColor: 'yellow',
}}
onLayout={(e) => console.log({ view: e.nativeEvent.layout })}>
<Text onLayout={(e) => console.log({ text: e.nativeEvent.layout })}>
Label
</Text>
</Animated.View>
<TextInput style={{ backgroundColor: 'blue' }} />
</View>
);
};
Example after the text has been scaled by half:
Note how the yellow view (Text) is no longer left aligned because of the scaling.
I've created a stack to explain what I am trying to accomplish:
https://snack.expo.dev/#bertrand-caron/trembling-beef-jerky
I'd like the Label View (yellow) to stay left align when scaled, instead of being shrunk centered inside the red view.
I have two solution:
1. For this I have created a snack: https://snack.expo.dev/PFN07UigC .
Let me know if it fits. For left aligning I just used justify-content:'flex-start' to its container.
2. If you wish to calculate it you know the initial size with onLayout and you have the scale let us assume that scale you are using is 0.5 and the onLayout gave you 100 then the margin-left that you are wanted is (width-(width*scale))/2 which is in our case 25. But I don't see any need for that.
In my opinion, you should make the <Animated.Text/> rather than <Animated.View/>. But it will also work.
If you don’t use alignSelf: 'flex-start' it will take the same space as the Parent View.
Here is my solution: https://snack.expo.dev/#fanish/textinput-animation
<Animated.Text
style={{
...textStyle,
transform: [{ scale }],
alignSelf: 'flex-start',
}}>
Label
</Animated.Text>
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>
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
So Ideally, When i scroll down, I want the header to disappear(slide down) and when I scroll up I want it to show (slide up). Idc where im at in the page. I just want the animation to fire when those 2 events occur. I see some apps have this but I can't think of how to replicate it. please help me set a basis for this
You can use Animated.FlatList or Animated.ScrollView to make the scroll view, and attach a callback to listen onScroll event when it is changed. Then, using interpolation to map value between y-axis and opacity.
searchBarOpacityAnim is a component's state. By using Animated.event, the state will be updated when a callback is called. Also, don't forget to set useNativeDriver to be true. I've attached the link to document below about why you have to set it.
<Animated.FlatList
...
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: searchBarOpacityAnim } } }],
{ useNativeDriver: true },
)}
...
/>
Then, use Animated.View wraps your component which you want to animate it. Use .interpolate to map value between the state and component's opacity like the example below...
<Animated.View
style={{
opacity: searchBarOpacityAnim.interpolate({
inputRange: [213, 215],
outputRange: [0, 1],
}),
}}
>
<SearchBar />
</Animated.View>
You can read more information about useNativeDriver, .interpolate, and Animated.event here.
https://facebook.github.io/react-native/docs/animated#using-the-native-driver
https://facebook.github.io/react-native/docs/animations#interpolation
https://facebook.github.io/react-native/docs/animated#handling-gestures-and-other-events
You can use Animated from 'react-native'
here an example changing the Topbar height:
import { Animated } from 'react-native';
define maxHeight and minHeight topbar
const HEADER_MAX_HEIGHT = 120;
const HEADER_MIN_HEIGHT = 48;
initialize a variable with the scrollY value
constructor(props) {
super(props);
this.state = {
scrollY: new Animated.Value(
Platform.OS === 'ios' ? -HEADER_MAX_HEIGHT : 0,
),
};
}
on render you can interpolate a value acording the scrollY Value
render() {
const { scrollY } = this.state;
// this will set a height for topbar
const headerHeight = scrollY.interpolate({
inputRange: [0, HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT],
outputRange: [HEADER_MAX_HEIGHT, HEADER_MIN_HEIGHT],
extrapolate: 'clamp',
});
// obs: the inputRange is the scrollY value, (starts on 0)
// and can go until (HEADER_MAX_HEIGHT - HEADER_MIN_HEIGHT)
// outputRange is the height that will set on topbar
// obs: you must add a onScroll function on a scrollView like below:
return (
<View>
<Animated.View style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
backgroundColor: '#2e4265',
height: headerHeight,
zIndex: 1,
flexDirection: 'row',
justifyContent: 'flex-start',
}}>
<Text>{title}</Text>
</Animated.View>
<ScrollView
style={{ flex: 1 }}
scrollEventThrottle={16}
onScroll={Animated.event(
[{ nativeEvent: { contentOffset: { y: this.state.scrollY } } }],
)}>
<View style={{ height: 1000 }} />
</ScrollView>
</View>
);
}
Is this doable? Check this link for example.
I've been trying to make it work. This is my current code:
constructor(props) {
super(props);
this.state = {
animationValue: new Animated.Value(0)
};
}
<Animated.View
style={{ flex: 0.15, backgroundColor: `rgba(0,0,0,${background}` }}
onPress={this.removeBackground}
>
<Text style={[{ textAlign: 'right' }>{hometeam.result} - {awayteam.result}</Text>
</Animated.View>
The background is a variable that looks like this:
const background = this.state.animationValue.interpolate({
inputRange: [0, 1],
outputRange: [0, 1]
});
What I want to happen is when you click on the animated view, I want the background color to fade. So I was trying to use rgba and change the alpha based on the background variable, which gave me an error.
This is my function to remove the background:
removeBackground = () => {
this.state.animationValue.setValue(0);
Animated.timing(this.state.animationValue, {
toValue: 1,
duration: 2000,
}).start();
}
Any clues how I could do this?