react-native-animated-header-flat-list
Version:
A React Native FlatList component with an animated collapsible header, featuring parallax effects, smooth title transitions, sticky component support, and customizable styles. Built with TypeScript and separate background/content layers in header.
125 lines (124 loc) • 4.66 kB
JavaScript
;
import { useHeaderHeight } from '@react-navigation/elements';
import { useCallback, useState } from 'react';
import { useWindowDimensions } from 'react-native';
import { interpolate, useAnimatedScrollHandler, useAnimatedStyle, useSharedValue } from 'react-native-reanimated';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
export const useAnimatedHeaderFlatListAnimatedStyles = ({
headerTitleFontSize,
navigationTitleFontSize,
navigationTitleTranslateX = 0,
navigationTitleTranslateY = 0
}) => {
const {
width: windowWidth
} = useWindowDimensions();
const scrollY = useSharedValue(0);
const navigationBarHeight = useHeaderHeight();
const safeAreaInsets = useSafeAreaInsets();
const [headerLayout, setHeaderLayout] = useState({
x: 0,
y: 0,
width: 0,
height: 0
});
const [headerTitleLayout, setHeaderTitleLayout] = useState({
x: 0,
y: 0,
width: 0,
height: 0
});
const [stickyComponentLayout, updateStickyComponentLayout] = useState({
x: 0,
y: 0,
width: 0,
height: 0
});
const distanceBetweenTitleAndNavigationBar = (navigationBarHeight - safeAreaInsets.top + headerTitleLayout.height) / 2 + headerTitleLayout.y - navigationBarHeight;
const navigationTitleOpacity = useSharedValue(0);
const stickyHeaderOpacity = useSharedValue(0);
const stickyComponentOpacity = useSharedValue(0);
const setStickyComponentLayout = useCallback(layout => {
updateStickyComponentLayout(layout);
stickyComponentOpacity.value = layout.height > 0 ? 1 : 0;
}, [updateStickyComponentLayout, stickyComponentOpacity]);
const navigationBarAnimatedStyle = useAnimatedStyle(() => {
return {
opacity: interpolate(scrollY.value, [0, headerLayout.height - navigationBarHeight * 2], [0, 1], 'clamp'),
marginBottom: Math.max(0, headerLayout.height - navigationBarHeight * 2 - scrollY.value),
height: navigationBarHeight
};
});
const navigationTitleAnimatedStyle = useAnimatedStyle(() => {
return {
opacity: navigationTitleOpacity.value,
transform: [{
translateX: navigationTitleTranslateX
}, {
translateY: navigationTitleTranslateY
}]
};
});
const headerTitleAnimatedStyle = useAnimatedStyle(() => {
return {
opacity: 1 - navigationTitleOpacity.value,
transform: [{
translateX: interpolate(scrollY.value, [0, distanceBetweenTitleAndNavigationBar], [0, windowWidth / 2 - headerTitleLayout.x - headerTitleLayout.width / 2 + navigationTitleTranslateX], 'clamp')
}, {
translateY: interpolate(scrollY.value, [0, distanceBetweenTitleAndNavigationBar], [0, navigationTitleTranslateY], 'clamp')
}, {
scale: interpolate(scrollY.value, [0, distanceBetweenTitleAndNavigationBar], [1, navigationTitleFontSize && headerTitleFontSize ? navigationTitleFontSize / headerTitleFontSize : 1], 'clamp')
}]
};
});
const stickyHeaderAnimatedStyle = useAnimatedStyle(() => {
return {
opacity: stickyHeaderOpacity.value
};
});
const headerContentAnimatedStyle = useAnimatedStyle(() => {
return {
opacity: interpolate(scrollY.value, [0, headerLayout.height - navigationBarHeight * 2], [1, 0], 'clamp')
};
});
const headerBackgroundAnimatedStyle = useAnimatedStyle(() => {
if (scrollY.value >= 0) {
return {};
}
return {
transform: [{
translateY: interpolate(scrollY.value, [scrollY.value, 0], [scrollY.value / 2, 0], 'clamp')
}, {
scale: interpolate(scrollY.value, [scrollY.value, 0], [1 - scrollY.value / (headerLayout.height - navigationBarHeight), 1], 'clamp')
}]
};
});
const stickyComponentAnimatedStyle = useAnimatedStyle(() => {
return {
opacity: stickyComponentOpacity.value
};
});
const scrollHandler = useAnimatedScrollHandler(event => {
scrollY.value = event.contentOffset.y;
navigationTitleOpacity.value = event.contentOffset.y >= distanceBetweenTitleAndNavigationBar ? 1 : 0;
stickyHeaderOpacity.value = event.contentOffset.y >= headerLayout.height - navigationBarHeight * 2 ? 1 : 0;
});
return {
scrollHandler,
navigationBarHeight,
headerLayout,
setHeaderLayout,
headerTitleLayout,
setHeaderTitleLayout,
stickyComponentLayout,
setStickyComponentLayout,
stickyComponentAnimatedStyle,
navigationBarAnimatedStyle,
navigationTitleAnimatedStyle,
headerTitleAnimatedStyle,
stickyHeaderAnimatedStyle,
headerContentAnimatedStyle,
headerBackgroundAnimatedStyle
};
};
//# sourceMappingURL=useAnimatedHeaderFlatListAnimatedStyles.js.map