├── .watchmanconfig ├── assets ├── cake.gif ├── icon.png └── splash.png ├── babel.config.js ├── react-native-animated-birthday-slider.png ├── .gitignore ├── .expo-shared └── assets.json ├── package.json ├── app.json ├── README.md └── App.js /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /assets/cake.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalinmiron/react-native-birthday-slider/HEAD/assets/cake.gif -------------------------------------------------------------------------------- /assets/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalinmiron/react-native-birthday-slider/HEAD/assets/icon.png -------------------------------------------------------------------------------- /assets/splash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalinmiron/react-native-birthday-slider/HEAD/assets/splash.png -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = function(api) { 2 | api.cache(true); 3 | return { 4 | presets: ['babel-preset-expo'], 5 | }; 6 | }; 7 | -------------------------------------------------------------------------------- /react-native-animated-birthday-slider.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/catalinmiron/react-native-birthday-slider/HEAD/react-native-animated-birthday-slider.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/**/* 2 | .expo/* 3 | npm-debug.* 4 | *.jks 5 | *.p8 6 | *.p12 7 | *.key 8 | *.mobileprovision 9 | *.orig.* 10 | web-build/ 11 | web-report/ 12 | -------------------------------------------------------------------------------- /.expo-shared/assets.json: -------------------------------------------------------------------------------- 1 | { 2 | "f9155ac790fd02fadcdeca367b02581c04a353aa6d5aa84409a59f6804c87acd": true, 3 | "89ed26367cdb9b771858e026f2eb95bfdb90e5ae943e716575327ec325f39c44": true 4 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "main": "node_modules/expo/AppEntry.js", 3 | "scripts": { 4 | "start": "expo start", 5 | "android": "expo start --android", 6 | "ios": "expo start --ios", 7 | "web": "expo start --web", 8 | "eject": "expo eject" 9 | }, 10 | "dependencies": { 11 | "expo": "^34.0.1", 12 | "react": "16.8.3", 13 | "react-dom": "^16.8.6", 14 | "react-native": "https://github.com/expo/react-native/archive/sdk-34.0.0.tar.gz", 15 | "react-native-web": "^0.11.4" 16 | }, 17 | "devDependencies": { 18 | "babel-preset-expo": "^6.0.0" 19 | }, 20 | "private": true 21 | } -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "expo": { 3 | "name": "react-native-birthday-slider", 4 | "slug": "react-native-birthday-slider", 5 | "privacy": "public", 6 | "sdkVersion": "34.0.0", 7 | "platforms": [ 8 | "ios", 9 | "android", 10 | "web" 11 | ], 12 | "version": "1.0.0", 13 | "orientation": "portrait", 14 | "icon": "./assets/icon.png", 15 | "splash": { 16 | "image": "./assets/splash.png", 17 | "resizeMode": "contain", 18 | "backgroundColor": "#ffffff" 19 | }, 20 | "updates": { 21 | "fallbackToCacheTimeout": 0 22 | }, 23 | "assetBundlePatterns": [ 24 | "**/*" 25 | ], 26 | "ios": { 27 | "supportsTablet": true 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Would you like to support me? 2 | 3 | Buy Me A Coffee 4 | 5 | - Paypal: https://paypal.me/catalinmiron 6 | 7 | # React Native Wheel of Fortune 8 | 9 | # Run on your device 10 | 11 | Snack: https://snack.expo.io/@catalinmiron/react-native-birthday-slider 12 | 13 | ### Youtube tutorial 14 | 15 | [![React Native Birthday Slider Youtube tutorial](react-native-animated-birthday-slider.png)](https://www.youtube.com/watch?v=vFtDPVnzFLM) 16 | 17 | In this lesson we’re going to be building a **Birthday Animated Slider Picker** in React Native using ScrollView from React Native and Expo for creating the react-native project. 18 | 19 | - Expo: https://expo.io/ 20 | - The Cake GIF: https://dribbble.com/shots/3623450-Birthday-Cake 21 | 22 | You can find me on: 23 | 24 | - Github: http://github.com/catalinmiron 25 | - Twitter: http://twitter.com/mironcatalin 26 | -------------------------------------------------------------------------------- /App.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { 3 | TextInput, 4 | SafeAreaView, 5 | ScrollView, 6 | Animated, 7 | Image, 8 | Dimensions, 9 | StyleSheet, 10 | View 11 | } from 'react-native'; 12 | const { width } = Dimensions.get('screen'); 13 | 14 | const minAge = 14; 15 | const segmentsLength = 91; 16 | const segmentWidth = 2; 17 | const segmentSpacing = 20; 18 | const snapSegment = segmentWidth + segmentSpacing; 19 | const spacerWidth = (width - segmentWidth) / 2; 20 | const rulerWidth = spacerWidth * 2 + (segmentsLength - 1) * snapSegment; 21 | const indicatorWidth = 100; 22 | const indicatorHeight = 80; 23 | const data = [...Array(segmentsLength).keys()].map(i => i + minAge); 24 | 25 | const Ruler = () => { 26 | return ( 27 | 28 | 29 | {data.map(i => { 30 | const tenth = i % 10 === 0; 31 | return ( 32 | 43 | ); 44 | })} 45 | 46 | 47 | ); 48 | }; 49 | 50 | export default class App extends React.Component { 51 | scrollViewRef = React.createRef(); 52 | textInputRef = React.createRef(); 53 | constructor(props) { 54 | super(props); 55 | 56 | this.state = { 57 | scrollX: new Animated.Value(0), 58 | initialAge: 25 59 | }; 60 | 61 | this.state.scrollX.addListener(({ value }) => { 62 | if (this.textInputRef && this.textInputRef.current) { 63 | this.textInputRef.current.setNativeProps({ 64 | text: `${Math.round(value / snapSegment) + minAge}` 65 | }); 66 | } 67 | }); 68 | } 69 | 70 | componentDidMount() { 71 | setTimeout(() => { 72 | if (this.scrollViewRef && this.scrollViewRef.current) { 73 | this.scrollViewRef.current._component.scrollTo({ 74 | x: (this.state.initialAge - minAge) * snapSegment, 75 | y: 0, 76 | animated: true 77 | }); 78 | } 79 | }, 1000); 80 | } 81 | 82 | render() { 83 | return ( 84 | 85 | 86 | 105 | 106 | 107 | 108 | 113 | 114 | 115 | 116 | ); 117 | } 118 | } 119 | 120 | const styles = StyleSheet.create({ 121 | indicatorWrapper: { 122 | position: 'absolute', 123 | left: (width - indicatorWidth) / 2, 124 | bottom: 34, 125 | alignItems: 'center', 126 | justifyContent: 'center', 127 | width: indicatorWidth 128 | }, 129 | segmentIndicator: { 130 | height: indicatorHeight, 131 | backgroundColor: 'turquoise' 132 | }, 133 | container: { 134 | flex: 1, 135 | backgroundColor: '#fff', 136 | position: 'relative' 137 | }, 138 | cake: { 139 | width, 140 | height: width * 1.2, 141 | resizeMode: 'cover' 142 | }, 143 | ruler: { 144 | width: rulerWidth, 145 | alignItems: 'flex-end', 146 | justifyContent: 'flex-start', 147 | flexDirection: 'row' 148 | }, 149 | segment: { 150 | width: segmentWidth 151 | }, 152 | scrollViewContainerStyle: { 153 | justifyContent: 'flex-end' 154 | }, 155 | ageTextStyle: { 156 | fontSize: 42, 157 | fontFamily: 'Menlo' 158 | }, 159 | spacer: { 160 | width: spacerWidth, 161 | backgroundColor: 'red' 162 | } 163 | }); 164 | --------------------------------------------------------------------------------