├── assets
├── icon.png
├── favicon.png
├── splash.png
└── adaptive-icon.png
├── tsconfig.json
├── babel.config.js
├── .gitignore
├── App.tsx
├── package.json
├── app.json
├── data.ts
└── HomeScreen.tsx
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iam-rohid/rn-scroll-animation/HEAD/assets/icon.png
--------------------------------------------------------------------------------
/assets/favicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iam-rohid/rn-scroll-animation/HEAD/assets/favicon.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iam-rohid/rn-scroll-animation/HEAD/assets/splash.png
--------------------------------------------------------------------------------
/assets/adaptive-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/iam-rohid/rn-scroll-animation/HEAD/assets/adaptive-icon.png
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "expo/tsconfig.base",
3 | "compilerOptions": {
4 | "strict": true
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/babel.config.js:
--------------------------------------------------------------------------------
1 | module.exports = function (api) {
2 | api.cache(true);
3 | return {
4 | presets: ["babel-preset-expo"],
5 | plugins: ["react-native-reanimated/plugin"],
6 | };
7 | };
8 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Learn more https://docs.github.com/en/get-started/getting-started-with-git/ignoring-files
2 |
3 | # dependencies
4 | node_modules/
5 |
6 | # Expo
7 | .expo/
8 | dist/
9 | web-build/
10 |
11 | # Native
12 | *.orig.*
13 | *.jks
14 | *.p8
15 | *.p12
16 | *.key
17 | *.mobileprovision
18 |
19 | # Metro
20 | .metro-health-check*
21 |
22 | # debug
23 | npm-debug.*
24 | yarn-debug.*
25 | yarn-error.*
26 |
27 | # macOS
28 | .DS_Store
29 | *.pem
30 |
31 | # local env files
32 | .env*.local
33 |
34 | # typescript
35 | *.tsbuildinfo
36 |
--------------------------------------------------------------------------------
/App.tsx:
--------------------------------------------------------------------------------
1 | import "react-native-gesture-handler";
2 | import { StatusBar } from "expo-status-bar";
3 | import { StyleSheet } from "react-native";
4 | import { GestureHandlerRootView } from "react-native-gesture-handler";
5 | import { SafeAreaProvider } from "react-native-safe-area-context";
6 | import HomeScreen from "./HomeScreen";
7 |
8 | export default function App() {
9 | return (
10 |
11 |
12 |
13 |
14 |
15 |
16 | );
17 | }
18 |
19 | const styles = StyleSheet.create({
20 | container: {
21 | flex: 1,
22 | },
23 | });
24 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "scroll-animation",
3 | "version": "1.0.0",
4 | "main": "node_modules/expo/AppEntry.js",
5 | "scripts": {
6 | "start": "expo start",
7 | "android": "expo start --android",
8 | "ios": "expo start --ios",
9 | "web": "expo start --web"
10 | },
11 | "dependencies": {
12 | "expo": "~49.0.15",
13 | "expo-status-bar": "~1.6.0",
14 | "react": "18.2.0",
15 | "react-native": "0.72.6",
16 | "react-native-reanimated": "~3.3.0",
17 | "react-native-gesture-handler": "~2.12.0",
18 | "react-native-safe-area-context": "4.6.3",
19 | "react-native-svg": "13.9.0"
20 | },
21 | "devDependencies": {
22 | "@babel/core": "^7.20.0",
23 | "@types/react": "~18.2.14",
24 | "typescript": "^5.1.3"
25 | },
26 | "private": true
27 | }
28 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "scroll-animation",
4 | "slug": "scroll-animation",
5 | "version": "1.0.0",
6 | "orientation": "portrait",
7 | "icon": "./assets/icon.png",
8 | "userInterfaceStyle": "light",
9 | "splash": {
10 | "image": "./assets/splash.png",
11 | "resizeMode": "contain",
12 | "backgroundColor": "#ffffff"
13 | },
14 | "assetBundlePatterns": [
15 | "**/*"
16 | ],
17 | "ios": {
18 | "supportsTablet": true,
19 | "infoPlist": {
20 | "CADisableMinimumFrameDurationOnPhone": true,
21 | },
22 | },
23 | "android": {
24 | "adaptiveIcon": {
25 | "foregroundImage": "./assets/adaptive-icon.png",
26 | "backgroundColor": "#ffffff"
27 | }
28 | },
29 | "web": {
30 | "favicon": "./assets/favicon.png"
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/data.ts:
--------------------------------------------------------------------------------
1 | export type Card = {
2 | id: number;
3 | balance: number;
4 | color: string;
5 | fgColor: string;
6 | };
7 |
8 | export const allCards: Card[] = [
9 | {
10 | id: 1,
11 | balance: 21424,
12 | color: "#4f46e5",
13 | fgColor: "#ffffff",
14 | },
15 | {
16 | id: 2,
17 | balance: 46700,
18 | color: "#2563eb",
19 | fgColor: "#ffffff",
20 | },
21 | {
22 | id: 3,
23 | balance: 30000,
24 | color: "#db2777",
25 | fgColor: "#ffffff",
26 | },
27 | {
28 | id: 4,
29 | balance: 430802,
30 | color: "#e11d48",
31 | fgColor: "#ffffff",
32 | },
33 | ];
34 |
35 | export type Expense = {
36 | id: number;
37 | title: string;
38 | type: string;
39 | price: string;
40 | };
41 |
42 | export const allExpenses: Expense[] = [
43 | {
44 | id: 1,
45 | title: "Amazon Echo Dot",
46 | type: "Amazon",
47 | price: "$29.99",
48 | },
49 | {
50 | id: 2,
51 | title: "Amazon Prime Membership",
52 | type: "Amazon",
53 | price: "$119",
54 | },
55 | {
56 | id: 3,
57 | title: "Netflix Subscription",
58 | type: "Mobile Subscription",
59 | price: "$12.99",
60 | },
61 | {
62 | id: 4,
63 | title: "iPhone 12 Pro",
64 | type: "Mobile",
65 | price: "$999",
66 | },
67 | {
68 | id: 5,
69 | title: "Kindle Paperwhite",
70 | type: "Amazon",
71 | price: "$149.99",
72 | },
73 | {
74 | id: 6,
75 | title: "Google Play Store Apps",
76 | type: "Mobile Subscription",
77 | price: "$5.99",
78 | },
79 | {
80 | id: 7,
81 | title: "Laptop Charger",
82 | type: "Electronics",
83 | price: "$39.99",
84 | },
85 | {
86 | id: 8,
87 | title: "Spotify Premium",
88 | type: "Mobile Subscription",
89 | price: "$9.99",
90 | },
91 | {
92 | id: 9,
93 | title: "Toilet Paper",
94 | type: "Household",
95 | price: "$4.99",
96 | },
97 | {
98 | id: 10,
99 | title: "Nike Running Shoes",
100 | type: "Sports",
101 | price: "$79.99",
102 | },
103 | {
104 | id: 11,
105 | title: "Hulu Subscription",
106 | type: "Mobile Subscription",
107 | price: "$7.99",
108 | },
109 | {
110 | id: 12,
111 | title: "Groceries",
112 | type: "Food",
113 | price: "$50",
114 | },
115 | {
116 | id: 13,
117 | title: "Smart Home Security Camera",
118 | type: "Electronics",
119 | price: "$149",
120 | },
121 | {
122 | id: 14,
123 | title: "Gym Membership",
124 | type: "Fitness",
125 | price: "$30",
126 | },
127 | {
128 | id: 15,
129 | title: "PlayStation 5",
130 | type: "Gaming",
131 | price: "$499.99",
132 | },
133 | {
134 | id: 16,
135 | title: "Ebook - The Great Gatsby",
136 | type: "Books",
137 | price: "$4.99",
138 | },
139 | {
140 | id: 17,
141 | title: "Home Decor Items",
142 | type: "Home",
143 | price: "$25",
144 | },
145 | {
146 | id: 18,
147 | title: "Car Insurance",
148 | type: "Insurance",
149 | price: "$100",
150 | },
151 | {
152 | id: 19,
153 | title: "Electricity Bill",
154 | type: "Utilities",
155 | price: "$75",
156 | },
157 | {
158 | id: 20,
159 | title: "Movie Tickets",
160 | type: "Entertainment",
161 | price: "$12.50",
162 | },
163 | ];
164 |
--------------------------------------------------------------------------------
/HomeScreen.tsx:
--------------------------------------------------------------------------------
1 | import { setStatusBarStyle } from "expo-status-bar";
2 | import {
3 | StyleSheet,
4 | Text,
5 | TouchableOpacity,
6 | View,
7 | useWindowDimensions,
8 | } from "react-native";
9 | import {
10 | SafeAreaView,
11 | useSafeAreaInsets,
12 | } from "react-native-safe-area-context";
13 | import Animated, {
14 | Extrapolate,
15 | interpolate,
16 | runOnJS,
17 | useAnimatedScrollHandler,
18 | useAnimatedStyle,
19 | useSharedValue,
20 | } from "react-native-reanimated";
21 | import FeatherIcons from "@expo/vector-icons/Feather";
22 | import { allExpenses, allCards, Expense } from "./data";
23 |
24 | const CARD_HEIGHT = 120;
25 | const TITLE_HEIGHT = 80;
26 |
27 | const HomeScreen = () => {
28 | const dimentions = useWindowDimensions();
29 | const insets = useSafeAreaInsets();
30 | const scrollY = useSharedValue(0);
31 | const expandMode = useSharedValue(false);
32 |
33 | const scrollHandler = useAnimatedScrollHandler({
34 | onScroll: (event) => {
35 | scrollY.value = event.contentOffset.y;
36 | if (event.contentOffset.y >= 80 && !expandMode.value) {
37 | expandMode.value = true;
38 | runOnJS(setStatusBarStyle)("light");
39 | }
40 | if (event.contentOffset.y < 80 && expandMode.value) {
41 | expandMode.value = false;
42 | runOnJS(setStatusBarStyle)("auto");
43 | }
44 | },
45 | });
46 |
47 | const cardScrollListStyle = useAnimatedStyle(() => ({
48 | top: Math.max(0, -(scrollY.value - 80)),
49 | }));
50 |
51 | const cardWrapperStyle = useAnimatedStyle(() => ({
52 | paddingHorizontal: interpolate(
53 | scrollY.value,
54 | [0, 80],
55 | [16, 0],
56 | Extrapolate.CLAMP
57 | ),
58 | }));
59 |
60 | const cardStyle = useAnimatedStyle(() => {
61 | const borderRadius = interpolate(
62 | scrollY.value,
63 | [60, 80],
64 | [24, 0],
65 | Extrapolate.CLAMP
66 | );
67 | return {
68 | borderTopLeftRadius: borderRadius,
69 | borderTopRightRadius: borderRadius,
70 | height: interpolate(
71 | scrollY.value,
72 | [0, 80],
73 | [CARD_HEIGHT, CARD_HEIGHT + insets.top],
74 | Extrapolate.CLAMP
75 | ),
76 | };
77 | });
78 |
79 | return (
80 | <>
81 | `expense-${item.id}`}
88 | renderItem={({ item }) => }
89 | ListHeaderComponent={() => (
90 |
91 |
92 | Hello Rohid
93 |
94 |
95 |
96 |
97 | Filter By
98 |
103 |
104 |
105 |
106 | )}
107 | />
108 | `card-${item.id}`}
116 | renderItem={({ item }) => (
117 |
124 |
127 |
130 |
131 | Current Balance
132 |
133 |
134 | ${item.balance.toLocaleString()}
135 |
136 |
137 |
138 | )}
139 | />
140 | >
141 | );
142 | };
143 |
144 | export default HomeScreen;
145 |
146 | function ExpanseItem({ expense }: { expense: Expense }) {
147 | return (
148 |
149 |
150 | {expense.title}
151 | {expense.type}
152 |
153 | {expense.price}
154 |
155 | );
156 | }
157 |
158 | const styles = StyleSheet.create({
159 | container: { flex: 1 },
160 | listHeaderTitleContainer: {
161 | paddingHorizontal: 16,
162 | paddingBottom: 16,
163 | height: TITLE_HEIGHT,
164 | justifyContent: "flex-end",
165 | },
166 | filterContainer: {
167 | flexDirection: "row",
168 | alignItems: "center",
169 | justifyContent: "flex-end",
170 | padding: 16,
171 | },
172 | filterButton: { flexDirection: "row", alignItems: "center", gap: 2 },
173 | filterButtonLabel: { opacity: 0.7, fontSize: 14 },
174 | filterButtonIcon: { opacity: 0.7 },
175 | listHeaderTitle: { fontSize: 36, fontWeight: "800" },
176 | listItem: {
177 | flexDirection: "row",
178 | alignItems: "center",
179 | paddingHorizontal: 16,
180 | paddingVertical: 12,
181 | },
182 | listItemContent: {
183 | flex: 1,
184 | },
185 | listItemTitle: { fontSize: 16, fontWeight: "500" },
186 | listItemSubtitle: { fontSize: 14, marginTop: 2, opacity: 0.5 },
187 | listItemValue: { fontSize: 16, fontWeight: "600", textAlign: "right" },
188 | cardsList: {
189 | position: "absolute",
190 | left: 0,
191 | right: 0,
192 | },
193 | cardWrapper: {
194 | justifyContent: "flex-end",
195 | },
196 | card: {
197 | justifyContent: "flex-end",
198 | padding: 16,
199 | position: "relative",
200 | overflow: "hidden",
201 | borderRadius: 24,
202 | },
203 | cardTitle: {
204 | fontSize: 16,
205 | marginBottom: 4,
206 | },
207 | cardCircle: {
208 | position: "absolute",
209 | width: 300,
210 | height: 300,
211 | borderRadius: 300,
212 | backgroundColor: "#ffffff",
213 | opacity: 0.1,
214 | top: -60,
215 | left: -100,
216 | },
217 | cardPriceText: {
218 | fontSize: 32,
219 | fontWeight: "700",
220 | },
221 | });
222 |
--------------------------------------------------------------------------------