├── .github └── demo.gif ├── assets ├── icon.png ├── favicon.png ├── splash.png └── adaptive-icon.png ├── src ├── assets │ └── map.jpg ├── screens │ └── Home.tsx └── components │ └── FloatingButton.tsx ├── tsconfig.json ├── babel.config.js ├── .gitignore ├── App.tsx ├── package.json ├── app.json └── README.md /.github/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/areasflavio/react-native-fab-button/HEAD/.github/demo.gif -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/areasflavio/react-native-fab-button/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/areasflavio/react-native-fab-button/HEAD/assets/favicon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/areasflavio/react-native-fab-button/HEAD/assets/splash.png -------------------------------------------------------------------------------- /src/assets/map.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/areasflavio/react-native-fab-button/HEAD/src/assets/map.jpg -------------------------------------------------------------------------------- /assets/adaptive-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/areasflavio/react-native-fab-button/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 | node_modules/ 2 | .expo/ 3 | dist/ 4 | npm-debug.* 5 | *.jks 6 | *.p8 7 | *.p12 8 | *.key 9 | *.mobileprovision 10 | *.orig.* 11 | web-build/ 12 | 13 | # macOS 14 | .DS_Store 15 | 16 | # Temporary files created by Metro to check the health of the file watcher 17 | .metro-health-check* 18 | -------------------------------------------------------------------------------- /App.tsx: -------------------------------------------------------------------------------- 1 | import { StatusBar } from 'expo-status-bar'; 2 | import { StyleSheet, View } from 'react-native'; 3 | 4 | import { Home } from './src/screens/Home'; 5 | 6 | export default function App() { 7 | return ( 8 | 9 | 10 | 11 | 12 | ); 13 | } 14 | 15 | const styles = StyleSheet.create({ 16 | container: { 17 | flex: 1, 18 | alignItems: 'center', 19 | }, 20 | }); 21 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rn-fab-button", 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": "~48.0.15", 13 | "expo-status-bar": "~1.4.4", 14 | "react": "18.2.0", 15 | "react-native": "0.71.7", 16 | "react-native-reanimated": "~2.14.4" 17 | }, 18 | "devDependencies": { 19 | "@babel/core": "^7.20.0", 20 | "@types/react": "~18.0.14", 21 | "typescript": "^4.9.4" 22 | }, 23 | "private": true 24 | } 25 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "rn-fab-button", 4 | "slug": "rn-fab-button", 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 | }, 20 | "android": { 21 | "adaptiveIcon": { 22 | "foregroundImage": "./assets/adaptive-icon.png", 23 | "backgroundColor": "#ffffff" 24 | } 25 | }, 26 | "web": { 27 | "favicon": "./assets/favicon.png" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/screens/Home.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import { ImageBackground, StyleSheet } from 'react-native'; 3 | import Animated, { 4 | useAnimatedStyle, 5 | withTiming, 6 | } from 'react-native-reanimated'; 7 | 8 | import { FloatingButton } from '../components/FloatingButton'; 9 | 10 | const AnimatedImageBackground = 11 | Animated.createAnimatedComponent(ImageBackground); 12 | 13 | export function Home() { 14 | const [modalOpen, setModalOpen] = useState(false); 15 | 16 | const animatedStyle = useAnimatedStyle(() => { 17 | return { 18 | opacity: withTiming(modalOpen ? 0.25 : 0.5), 19 | }; 20 | }); 21 | 22 | function onPress() { 23 | setModalOpen((curr) => !curr); 24 | } 25 | 26 | return ( 27 | <> 28 | 33 | 34 | 35 | ); 36 | } 37 | const styles = StyleSheet.create({ 38 | image: { 39 | flex: 1, 40 | width: '100%', 41 | height: '100%', 42 | }, 43 | }); 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # React-Native-Fab-Button 2 | 3 |

4 | A simple animated floating action button screen Built with React Native, Expo and Reanimated. 5 |

6 | 7 |

8 | GitHub top language 9 | 10 | GitHub language count 11 | 12 | 13 | GitHub last commit 14 | 15 |

16 | 17 |

18 | Features  |   19 | Technologies  |   20 | Installation 21 |

22 | 23 |

24 | demo 25 |

26 | 27 |
28 | 29 | # :star: Features 30 | 31 | [(Back to top)](#React-Native-Fab-Button) 32 | 33 | Some key features are: 34 | 35 | - Customizable buttons 36 | - Smooth animations 37 | 38 | The application is built using React Native with Expo framework. 39 | The entire codebase is written using Typescript. 40 | 41 |
42 | 43 | # :keyboard: Technologies 44 | 45 | [(Back to top)](#React-Native-Fab-Button) 46 | 47 | This is what I used and learned with this project: 48 | 49 | - [x] React Native 50 | - [x] Expo 51 | - [x] Reanimated 52 | - [x] Typescript 53 | 54 |
55 | 56 | # :computer_mouse: Installation 57 | 58 | [(Back to top)](#React-Native-Fab-Button) 59 | 60 | To use this project, first you need NodeJS installed in your device, 61 | then you can follow the commands below: 62 | 63 | ```bash 64 | # Clone this repository 65 | git clone https://github.com/areasflavio/react-native-fab-button.git 66 | 67 | # Go into the repository 68 | cd react-native-fab-button 69 | 70 | # Install dependencies for the application 71 | yarn install 72 | 73 | # To start the development server, run the following command 74 | npm run start 75 | 76 | # You start the emulator following the terminal instructions or: 77 | npm run android # for android emulator 78 | 79 | npm run ios # for ios emulator 80 | ``` 81 | 82 | # :man_technologist: Author 83 | 84 | [(Back to top)](#React-Native-Fab-Button) 85 | 86 | Build by Flávio Arêas 👋 [Get in touch!](https://www.linkedin.com/in/areasflavio/) 87 | -------------------------------------------------------------------------------- /src/components/FloatingButton.tsx: -------------------------------------------------------------------------------- 1 | import { Entypo } from '@expo/vector-icons'; 2 | import React, { useState } from 'react'; 3 | import { StyleSheet, TouchableOpacity, View, ViewProps } from 'react-native'; 4 | import Animated, { 5 | Extrapolate, 6 | interpolate, 7 | useAnimatedStyle, 8 | useSharedValue, 9 | withSpring, 10 | } from 'react-native-reanimated'; 11 | 12 | interface FloatingButtonProps extends ViewProps { 13 | onPress: () => void; 14 | } 15 | 16 | export function FloatingButton({ 17 | onPress, 18 | style, 19 | ...rest 20 | }: FloatingButtonProps) { 21 | const [isOpen, setIsOpen] = useState(false); 22 | const animation = useSharedValue(0); 23 | 24 | const rotationAnimatedStyle = useAnimatedStyle(() => { 25 | return { 26 | transform: [ 27 | { 28 | rotate: withSpring(isOpen ? '45deg' : '0deg'), 29 | }, 30 | ], 31 | }; 32 | }); 33 | 34 | const pinAnimatedStyle = useAnimatedStyle(() => { 35 | const translateYAnimation = interpolate( 36 | animation.value, 37 | [0, 1], 38 | [0, -20], 39 | Extrapolate.CLAMP 40 | ); 41 | 42 | return { 43 | transform: [ 44 | { 45 | scale: withSpring(animation.value), 46 | }, 47 | { 48 | translateY: withSpring(translateYAnimation), 49 | }, 50 | ], 51 | }; 52 | }); 53 | 54 | const thumbAnimatedStyle = useAnimatedStyle(() => { 55 | const translateYAnimation = interpolate( 56 | animation.value, 57 | [0, 1], 58 | [0, -30], 59 | Extrapolate.CLAMP 60 | ); 61 | 62 | return { 63 | transform: [ 64 | { 65 | scale: withSpring(animation.value), 66 | }, 67 | { 68 | translateY: withSpring(translateYAnimation), 69 | }, 70 | ], 71 | }; 72 | }); 73 | 74 | const heartAnimatedStyle = useAnimatedStyle(() => { 75 | const translateYAnimation = interpolate( 76 | animation.value, 77 | [0, 1], 78 | [0, -40], 79 | Extrapolate.CLAMP 80 | ); 81 | 82 | return { 83 | transform: [ 84 | { 85 | scale: withSpring(animation.value), 86 | }, 87 | { 88 | translateY: withSpring(translateYAnimation), 89 | }, 90 | ], 91 | }; 92 | }); 93 | 94 | const opacityAnimatedStyle = useAnimatedStyle(() => { 95 | const opacityAnimation = interpolate( 96 | animation.value, 97 | [0, 0.5, 1], 98 | [0, 0, 1], 99 | Extrapolate.CLAMP 100 | ); 101 | 102 | return { 103 | opacity: withSpring(opacityAnimation), 104 | }; 105 | }); 106 | 107 | function toggleMenu() { 108 | onPress(); 109 | setIsOpen((current) => { 110 | animation.value = current ? 0 : 1; 111 | return !current; 112 | }); 113 | } 114 | 115 | return ( 116 | 117 | 118 | 126 | 127 | 128 | 129 | 130 | 131 | 139 | 140 | 141 | 142 | 143 | 144 | 152 | 153 | 154 | 155 | 156 | 157 | 160 | 161 | 162 | 163 | 164 | ); 165 | } 166 | 167 | const styles = StyleSheet.create({ 168 | container: { 169 | alignItems: 'center', 170 | position: 'absolute', 171 | justifyContent: 'center', 172 | }, 173 | button: { 174 | width: 60, 175 | height: 60, 176 | borderRadius: 30, 177 | alignItems: 'center', 178 | justifyContent: 'center', 179 | shadowRadius: 10, 180 | shadowColor: '#f02a4b', 181 | shadowOpacity: 0.3, 182 | shadowOffset: { height: 10, width: 10 }, 183 | elevation: 10, 184 | }, 185 | menu: { 186 | backgroundColor: '#f02a4b', 187 | }, 188 | secondary: { 189 | width: 48, 190 | height: 48, 191 | borderRadius: 24, 192 | backgroundColor: '#ffffff', 193 | }, 194 | }); 195 | --------------------------------------------------------------------------------