├── .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 |
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 | [](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 |
--------------------------------------------------------------------------------