31
loading...
This website collects cookies to deliver better user experience
absolute
style property.Animated.ScrollView
will need a:contentContainerStyle={{paddingTop: this.state.headerHeight}}
headerHeight
as well. For this to happen, we will pass an onLayout
callback function to the header component and will call it inside CollapsibleHeader
component later on:onHeaderLayout = (headerHeight) => {
this.setState({
headerHeight,
});
};
// A bunch of code we don't need yet
render() {
// A bunch of components and props again not needed yet...
<CollapsibleHeader
// A ton of props we don't care about yet...
onLayout={this.onHeaderLayout}
..
/>
}
onScroll={Animated.event(
[{nativeEvent: {contentOffset: {y: this.scrollY}}}],
{useNativeDriver: true},
)}
scrollY
is an Animated
value defined at the top of the container component:this.scrollY = new Animated.Value(0)
CollapsibleHeader
component will need to know about the scroll value to work. Therefore we will add this prop to the component which is in the container component:scrollY={this.scrollY}
onLayout
callback from the previous section? Here's where we're going to define the function itself and fetch the required values and eventually inform the parent about it:onLayout = ({
nativeEvent: {
layout: { y, height },
},
}) => {
this.setState({
layoutHeight: height,
})
this.props.onLayout && this.props.onLayout(height)
}
Animated.View
component, which navigates the animated transformation while scrolling the content.diffClamp
.Animated
function does, let's start with clamping itself.In computer graphics, clamping is the process of limiting a position to an area. In general, we use clamping to restrict a value to a given range.
Wikipedia
function clamp(x, min, max):
if (x < min) then
x = min
else if (x > max) then
x = max
return x
x
would be the scrollY
value, obviously. But this simple clamping is not enough.scrollY
value. It would've been desirable to only display the header on the top of the page. And then hide it when the user scrolls past the header height.scrollY
value. We care about how much it's changed compared to a moment ago.diffClamp
does for us. This function internally subtracts the two continuous scrollY
values and feeds them to the clamp function. So this way, we will always have a value between 0
and headerHeight
no matter where on the list.clampedScroll
value in the componentDidUpdate()
:componentDidUpdate() {
const {scrollY, stickyHeaderHeight} = this.props;
const {layoutHeight, clampedScroll} = this.state;
if (stickyHeaderHeight && layoutHeight && !clampedScroll) {
this.setState({
clampedScroll: Animated.diffClamp(
scrollY,
0,
layoutHeight - stickyHeaderHeight,
),
});
}
}
min
value equal to 0
. We want the calculations to start at the top of the list when the user has made no motion yet. And we stop the range when the user scrolls about the height of the header. Since we want to display the sticky bar all the way around, we're subtracting the height of the bar here.setStickyHeight
method to the parent, and the parent passes it to the sticky bar component.TabBar
component's onLayout
function eventually and gives us the height. We will go over this in more detail in the next section.setStickyHeight
method in the ComponentDidUpdate()
when the stickyHeight
prop is available through the parent.render
method finally!translateY
value of the wrapper View
. Meaning moving it upward and downward.translateY
value equal to the layoutHeight - stickyHeight
to move it out of the view. And vice versa to display it again.clampedScroll
and the translateY
is equal but reverse in direction.scrollY
value increases). And we want to display the header as soon as the user scrolls up. (therefore decreasing the scrollY
value).const translateY =
clampedScroll && layoutHeight && stickyHeight
? Animated.multiply(clampedScroll, -1)
: 0
interpolate
method.style
array, alongside the onLayout
prop:return (
<Animated.View
style={[styles.container, { transform: [{ translateY }] }]}
onLayout={this.onLayout}
>
{this.props.children}
</Animated.View>
)
absolute
positioning for the header component, we're going to use this container style:container: {
position: 'absolute',
top: 0,
left: 0,
right: 0,
backgroundColor: 'black',
zIndex: 10,
},
<CollapsibleHeader>
component. As such:<CollapsibleHeader
...
>
<Text style={styles.sectionTitle}>My Awesome App</Text>
<TabBar onLayout={this.onStickyHeaderLayout} />
</CollapsibleHeader>
onLayout
callback function of the parent. Which is similar to the one we've used for the CollapsibleHeader
component:onStickyHeaderLayout = stickyHeaderHeight => {
this.setState({
stickyHeaderHeight,
})
this.header?.current?.setStickyHeight(stickyHeaderHeight)
}
setStickyHeight
function of the <CollapsibleHeader>
and why we need it.<TabBar>
component needs an onLayout
function which follows the same patterns:onViewLayout = ({
nativeEvent: {
layout: { height, y },
},
}) => {
const { onLayout } = this.props
onLayout && onLayout(height, y)
}
Animated
API.interpolate
approach.