Skip to content

Commit 92a02bd

Browse files
authored
feat: New <AnimatedFlatList/> component (#34)
* feat: first flatlist implementation * refactor: rename AnimatedHeader to AnimatedNavbar * refactor: rename AnimatedNavbar props * feat: new reusable Animated header * chore: reformat code * feat: update flatlist example * fix: add semicolon * docs: update readme * docs: update readme * fix: revert color tomato
1 parent b7908f0 commit 92a02bd

File tree

13 files changed

+332
-152
lines changed

13 files changed

+332
-152
lines changed

README.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
[![NPM version][npm-image]][npm-url] [![npm][npm-downloads]][npm-url] [![npm][license-url]][npm-url] [![npm][types-url]][npm-url] [![runs with expo][expo-image]][expo-url]
44

5-
A performant animated ScrollView component that:
6-
* 🔥Animates an image or a custom component into a navbar header
7-
* 🔥Supports bounce animation on scroll down
8-
* 🔥Supports both iOS and Android devices
5+
Performant animated scroll view components that:
6+
* 🔥Support `FlatList` and `ScrollView` scrolling interactions.
7+
* 🔥Animate an image or a custom component into a navbar header
8+
* 🔥Support bounce animation on scroll down
9+
* 🔥Support both iOS and Android devices
910

1011
![React Native Animated Header ScrollView](./preview-ios.gif)
1112

@@ -37,6 +38,33 @@ export const App = () => {
3738
};
3839
```
3940

41+
```typescript
42+
import { Card, TopNavBar, HeaderNavBar } from '../components';
43+
import { AnimatedScrollView } from '@kanelloc/react-native-animated-header-scroll-view';
44+
import * as React from 'react';
45+
46+
export const App = () => {
47+
const data = Array.from(Array(20).keys());
48+
const renderItem = ({ item }: any) => {
49+
return (
50+
<View>
51+
<Card item={item} />
52+
</View>
53+
);
54+
};
55+
56+
return (
57+
<AnimatedFlatList
58+
headerImage={require('../../assets/cabin.jpg')}
59+
data={data}
60+
renderItem={renderItem}
61+
HeaderNavbarComponent={<HeaderNavBar />}
62+
TopNavBarComponent={<TopNavBar />}
63+
/>
64+
);
65+
};
66+
```
67+
4068
You can find a set of detailed examples [here](https://github.com/kanelloc/react-native-animated-header-scroll-view/tree/main/example)
4169

4270
Also a running snack [here](https://snack.expo.dev/ukGomwbdE)

example/src/App.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import type { RootStackParamList } from './types';
99
import { ImageForegroundExample } from './screens/ImageForegroundExample';
1010
import { OnlyForegroundExample } from './screens/OnlyForegroundExample';
1111
import { RefreshControlExample } from './screens/RefreshControlExample';
12+
import { HeaderNavbarFlatListExample } from './screens/HeaderNavbarFlatListExample';
1213

1314
const App = () => {
1415
const Stack = createNativeStackNavigator<RootStackParamList>();
@@ -41,6 +42,10 @@ const App = () => {
4142
name="RefreshControlExample"
4243
component={RefreshControlExample}
4344
/>
45+
<Stack.Screen
46+
name="HeaderNavbarFlatListExample"
47+
component={HeaderNavbarFlatListExample}
48+
/>
4449
</Stack.Navigator>
4550
</NavigationContainer>
4651
</SafeAreaProvider>

example/src/screens/ExamplesDirectory.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ export const ExamplesDirectory = ({ navigation }: Props) => {
1616
{ screen: SCREENS.IMAGE_FOREGROUND_EXAMPLE },
1717
{ screen: SCREENS.ONLY_FOREGROUND_EXAMPLE },
1818
{ screen: SCREENS.REFRESH_CONTROL_EXAMPLE },
19+
{ screen: SCREENS.HEADER_NAVBAR_FLATLIST_EXAMPLE },
1920
];
2021

2122
return (
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
import * as React from 'react';
2+
import { RefreshControl, StyleSheet, View } from 'react-native';
3+
import { AnimatedFlatList } from '@kanelloc/react-native-animated-header-scroll-view';
4+
import { data } from '../utils';
5+
import { Card, HeaderNavBar, TopNavBar } from '../components';
6+
import { useRefresh } from '@react-native-community/hooks';
7+
8+
const fetch = () => {
9+
return new Promise((resolve) => setTimeout(resolve, 5000));
10+
};
11+
export const HeaderNavbarFlatListExample = () => {
12+
const { isRefreshing, onRefresh } = useRefresh(fetch);
13+
const renderItem = ({ item }: any) => {
14+
return (
15+
<View>
16+
<Card item={item} />
17+
</View>
18+
);
19+
};
20+
21+
return (
22+
<View>
23+
<AnimatedFlatList
24+
refreshControl={
25+
<RefreshControl
26+
refreshing={isRefreshing}
27+
onRefresh={onRefresh}
28+
style={styles.refresh}
29+
progressViewOffset={32} // Add offset to position correctly progressView in iOS
30+
tintColor="black"
31+
/>
32+
}
33+
headerImage={require('../../assets/cabin.jpg')}
34+
data={data}
35+
renderItem={renderItem}
36+
HeaderNavbarComponent={<HeaderNavBar />}
37+
TopNavBarComponent={<TopNavBar />}
38+
/>
39+
</View>
40+
);
41+
};
42+
43+
const styles = StyleSheet.create({
44+
refresh: {
45+
zIndex: 10,
46+
},
47+
});

example/src/types.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,5 @@ export type RootStackParamList = {
66
ImageForegroundExample: undefined;
77
OnlyForegroundExample: undefined;
88
RefreshControlExample: undefined;
9+
HeaderNavbarFlatListExample: undefined;
910
};

example/src/utils/enums.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ export enum SCREENS {
55
IMAGE_FOREGROUND_EXAMPLE = 'ImageForegroundExample',
66
ONLY_FOREGROUND_EXAMPLE = 'OnlyForegroundExample',
77
REFRESH_CONTROL_EXAMPLE = 'RefreshControlExample',
8+
HEADER_NAVBAR_FLATLIST_EXAMPLE = 'HeaderNavbarFlatListExample',
89
}
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import React from 'react';
2+
import { Animated } from 'react-native';
3+
import type { AnimatedFlatListViewProps } from '../types';
4+
import AnimatedNavbar from './AnimatedNavbar';
5+
import { HEADER_HEIGHT, IMG_HEADER_HEIGHT } from '../constants';
6+
import { useAnimateScrollView } from '../hooks/useAnimateScrollView';
7+
import { AnimatedHeader } from './AnimatedHeader';
8+
9+
export const AnimatedFlatList = ({
10+
headerMaxHeight,
11+
topBarHeight,
12+
disableScale,
13+
TopNavBarComponent,
14+
HeaderNavbarComponent,
15+
headerImage,
16+
imageStyle,
17+
HeaderComponent,
18+
...props
19+
}: AnimatedFlatListViewProps) => {
20+
const imageHeight = headerMaxHeight || IMG_HEADER_HEIGHT;
21+
const headerNavHeight = topBarHeight || HEADER_HEIGHT;
22+
const [scroll, onScroll, scale, translateYDown, translateYUp] =
23+
useAnimateScrollView(imageHeight, disableScale);
24+
25+
return (
26+
<>
27+
<Animated.FlatList
28+
{...props}
29+
onScroll={onScroll}
30+
ListHeaderComponent={
31+
<AnimatedHeader
32+
HeaderComponent={HeaderComponent}
33+
headerImage={headerImage}
34+
imageStyle={imageStyle}
35+
imageHeight={imageHeight}
36+
translateYDown={translateYDown}
37+
translateYUp={translateYUp}
38+
scale={scale}
39+
/>
40+
}
41+
/>
42+
<AnimatedNavbar
43+
headerHeight={headerNavHeight}
44+
scroll={scroll}
45+
imageHeight={imageHeight}
46+
OverflowHeaderComponent={HeaderNavbarComponent}
47+
TopNavbarComponent={TopNavBarComponent}
48+
/>
49+
</>
50+
);
51+
};

src/components/AnimatedHeader.tsx

Lines changed: 81 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -1,71 +1,94 @@
1+
import {
2+
Animated,
3+
ImageBackground,
4+
StyleSheet,
5+
useWindowDimensions,
6+
View,
7+
} from 'react-native';
18
import React from 'react';
2-
import { StyleSheet, Animated } from 'react-native';
3-
import { useAnimateNavbar } from '../hooks/useAnimateNavbar';
49
import type { AnimatedHeaderProps } from '../types';
510

6-
const AnimatedHeader = ({
7-
scroll,
8-
imageHeight,
9-
OverflowHeaderComponent,
11+
export const AnimatedHeader = ({
1012
HeaderComponent,
11-
headerHeight,
13+
headerImage,
14+
imageHeight,
15+
translateYUp,
16+
translateYDown,
17+
scale,
18+
imageStyle,
1219
}: AnimatedHeaderProps) => {
13-
const [headerOpacity, overflowHeaderOpacity] = useAnimateNavbar(
14-
scroll,
15-
imageHeight,
16-
headerHeight
17-
);
18-
20+
const { width } = useWindowDimensions();
21+
const AnimatedImageBackground =
22+
Animated.createAnimatedComponent(ImageBackground);
1923
return (
20-
<>
21-
<Animated.View
22-
style={[
23-
styles.container,
24-
styles.header,
25-
{
26-
zIndex: headerOpacity,
27-
height: headerHeight,
28-
opacity: headerOpacity,
29-
},
30-
]}
31-
>
32-
{HeaderComponent}
33-
</Animated.View>
34-
<Animated.View
35-
style={[
36-
styles.container,
37-
styles.overflowHeader,
38-
{
39-
zIndex: overflowHeaderOpacity,
40-
height: headerHeight,
41-
opacity: overflowHeaderOpacity,
42-
},
43-
]}
44-
>
45-
{OverflowHeaderComponent}
46-
</Animated.View>
47-
</>
24+
<View
25+
style={[
26+
styles.imgContainer,
27+
{
28+
marginTop: -imageHeight * 4,
29+
paddingTop: imageHeight * 4,
30+
},
31+
]}
32+
>
33+
{HeaderComponent ? (
34+
<>
35+
{headerImage ? (
36+
<AnimatedImageBackground
37+
source={headerImage}
38+
style={[
39+
{ height: imageHeight, width: width * 1.2 },
40+
{
41+
transform: [
42+
{ scale: scale },
43+
{ translateY: translateYUp },
44+
{ translateY: translateYDown },
45+
],
46+
},
47+
imageStyle,
48+
]}
49+
>
50+
{HeaderComponent}
51+
</AnimatedImageBackground>
52+
) : (
53+
<Animated.View
54+
style={[
55+
{ height: imageHeight, width: width * 1.2 },
56+
{
57+
transform: [
58+
{ scale },
59+
{ translateY: translateYUp },
60+
{ translateY: translateYDown },
61+
],
62+
},
63+
]}
64+
>
65+
{HeaderComponent}
66+
</Animated.View>
67+
)}
68+
</>
69+
) : (
70+
<Animated.Image
71+
source={headerImage}
72+
style={[
73+
{ height: imageHeight, width: width * 1.2 },
74+
{
75+
transform: [
76+
{ scale },
77+
{ translateY: translateYUp },
78+
{ translateY: translateYDown },
79+
],
80+
},
81+
imageStyle,
82+
]}
83+
/>
84+
)}
85+
</View>
4886
);
4987
};
5088

5189
const styles = StyleSheet.create({
52-
container: {
53-
paddingTop: 32,
54-
position: 'absolute',
55-
elevation: 2,
56-
top: 0,
57-
width: '100%',
58-
backgroundColor: 'white',
90+
imgContainer: {
5991
alignItems: 'center',
60-
justifyContent: 'center',
61-
},
62-
header: {
63-
borderBottomWidth: StyleSheet.hairlineWidth,
64-
borderBottomColor: '#a4a4a4',
65-
},
66-
overflowHeader: {
67-
backgroundColor: 'transparent',
92+
overflow: 'hidden',
6893
},
6994
});
70-
71-
export default AnimatedHeader;

0 commit comments

Comments
 (0)