├── .DS_Store
├── .expo-shared
└── assets.json
├── .github
└── FUNDING.yml
├── .gitignore
├── .watchmanconfig
├── App.js
├── README.md
├── app.json
├── assets
├── icon.png
├── mars_planet.png
└── splash.png
├── babel.config.js
├── package.json
├── react-native-shared-element-transition.png
└── yarn.lock
/.DS_Store:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/catalinmiron/react-native-shared-element-transition/b07fd26484e7ca1f37a6e9d6b1e2b7d7d7ecd08b/.DS_Store
--------------------------------------------------------------------------------
/.expo-shared/assets.json:
--------------------------------------------------------------------------------
1 | {
2 | "f9155ac790fd02fadcdeca367b02581c04a353aa6d5aa84409a59f6804c87acd": true,
3 | "89ed26367cdb9b771858e026f2eb95bfdb90e5ae943e716575327ec325f39c44": true
4 | }
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: catalinmiron # Replace with a single Patreon username
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: catalinmiron # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: ['https://www.buymeacoffee.com/catalinmiron']# Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.watchmanconfig:
--------------------------------------------------------------------------------
1 | {}
2 |
--------------------------------------------------------------------------------
/App.js:
--------------------------------------------------------------------------------
1 | // Inspiration: https://dribbble.com/shots/6520262-Mars
2 | // Planet image: https://pngimg.com/download/61156
3 | import React from 'react';
4 | import {
5 | ScrollView,
6 | Dimensions,
7 | TouchableOpacity,
8 | TouchableWithoutFeedback,
9 | StyleSheet,
10 | Text,
11 | View,
12 | Image,
13 | Platform,
14 | StatusBar
15 | } from 'react-native';
16 | import { AppLoading } from 'expo';
17 | import { Asset } from 'expo-asset';
18 | import { createAppContainer, createStackNavigator } from 'react-navigation';
19 | import { Ionicons } from '@expo/vector-icons';
20 | import * as Animatable from 'react-native-animatable';
21 | import { createFluidNavigator, Transition } from 'react-navigation-fluid-transitions';
22 | const { width, height } = Dimensions.get('screen');
23 |
24 | const smallPlanet = 300;
25 | const bigPlanet = 900;
26 | const ratio = bigPlanet / smallPlanet;
27 | const dotSize = 14;
28 | const rotation = '60deg';
29 |
30 | const numberOfLocations = 10;
31 | const timings = [...Array(numberOfLocations).keys()].map(() => Math.round(Math.random() * 80) + 10);
32 | const coordinates = [...Array(numberOfLocations).keys()].map(() => {
33 | const angle = Math.random() * 2 * Math.PI;
34 | const maxRadius = smallPlanet / 2 - dotSize * 2;
35 | const radius = maxRadius * Math.sqrt(Math.random());
36 |
37 | // Cartesian coodinates.
38 | return {
39 | x: radius * Math.cos(angle) + smallPlanet / 2,
40 | y: radius * Math.sin(angle) + smallPlanet / 2
41 | };
42 | });
43 |
44 | const myCustomTransition = transitionInfo => {
45 | const { progress, start, end } = transitionInfo;
46 | const opacity = progress.interpolate({
47 | inputRange: [0, start, end, 1],
48 | outputRange: [1, 1, 0, 0]
49 | });
50 | const translateX = progress.interpolate({
51 | inputRange: [0, start, end, 1],
52 | outputRange: [0, 0, 100, 100]
53 | });
54 |
55 | return { opacity, transform: [{ translateX }] };
56 | };
57 |
58 | function cacheImages(images) {
59 | return images.map(image => {
60 | if (typeof image === 'string') {
61 | return Image.prefetch(image);
62 | } else {
63 | return Asset.fromModule(image).downloadAsync();
64 | }
65 | });
66 | }
67 |
68 | const Screen1 = ({ navigation }) => {
69 | return (
70 |
71 |
78 |
79 |
80 |
81 |
82 |
83 | navigation.navigate('Screen2')} style={{ position: 'absolute', bottom: 70 }}>
84 |
85 |
86 |
87 |
88 | );
89 | };
90 |
91 | const Screen2 = ({ navigation }) => {
92 | return (
93 |
94 |
95 |
96 | {}} />
97 |
98 |
99 | {coordinates.map(({ x, y }, i) => {
100 | return (
101 | navigation.navigate('Screen3', { coordinates: { x, y } })}
103 | hitSlop={{ top: 20, left: 20, bottom: 20, right: 20 }}
104 | style={{ position: 'absolute' }}
105 | key={i}
106 | >
107 |
126 |
127 | );
128 | })}
129 |
130 |
131 |
132 | {
134 | navigation.navigate('Screen1');
135 | }}
136 | >
137 |
138 |
139 |
140 |
141 |
142 | {timings.map((time, i) => {
143 | return (
144 |
152 | {time}m
153 |
154 | );
155 | })}
156 |
157 |
158 | );
159 | };
160 |
161 | const Screen3 = ({ navigation }) => {
162 | const { x, y } = navigation.state.params.coordinates;
163 | return (
164 |
165 |
171 |
172 |
173 |
174 |
175 |
188 |
189 |
190 | navigation.navigate('Screen2')}>
191 |
192 |
193 |
194 |
195 |
196 | );
197 | };
198 |
199 | const AppContainer = createAppContainer(
200 | createFluidNavigator(
201 | {
202 | Screen1: {
203 | screen: Screen1
204 | },
205 | Screen2: {
206 | screen: Screen2
207 | },
208 | Screen3: {
209 | screen: Screen3
210 | }
211 | },
212 | {
213 | headerMode: 'none',
214 | initialRouteName: 'Screen1'
215 | }
216 | )
217 | );
218 |
219 | export default class App extends React.Component {
220 | state = {
221 | isReady: false
222 | };
223 |
224 | async _loadAssetsAsync() {
225 | const imageAssets = cacheImages([require('./assets/mars_planet.png')]);
226 |
227 | await Promise.all([...imageAssets]);
228 | }
229 |
230 | componentWillMount() {
231 | StatusBar.setHidden(true);
232 | }
233 |
234 | render() {
235 | if (!this.state.isReady) {
236 | return (
237 | this.setState({ isReady: true })}
240 | onError={console.warn}
241 | />
242 | );
243 | }
244 | return (
245 |
246 |
247 |
248 | );
249 | }
250 | }
251 |
252 | const styles = StyleSheet.create({
253 | container: {
254 | flex: 1,
255 | alignItems: 'center',
256 | justifyContent: 'center',
257 | backgroundColor: '#000'
258 | },
259 | marsPlanetScreen1: {
260 | width: height,
261 | height: height,
262 | resizeMode: 'contain',
263 | position: 'absolute',
264 | bottom: -height
265 | },
266 | marsPlanetScreen2: {
267 | width: smallPlanet,
268 | height: smallPlanet,
269 | resizeMode: 'contain',
270 | transform: [
271 | {
272 | rotate: rotation
273 | }
274 | ]
275 | },
276 | marsPlanetScreen3: {
277 | resizeMode: 'contain',
278 | width: bigPlanet,
279 | height: bigPlanet,
280 | transform: [{ rotate: rotation }]
281 | },
282 | headerNavigation: {
283 | position: 'absolute',
284 | top: 0,
285 | left: 0,
286 | width,
287 | alignItems: 'flex-start',
288 | justifyContent: 'center',
289 | padding: 20
290 | },
291 |
292 | timingScrollViewContainer: { position: 'absolute', bottom: 0, left: 0 },
293 | timingBox: {
294 | marginRight: 20,
295 | borderWidth: 1,
296 | borderColor: '#fff',
297 | borderRadius: 10,
298 | padding: 10,
299 | height: 120,
300 | width: 110,
301 | alignItems: 'flex-end',
302 | justifyContent: 'flex-end'
303 | },
304 | timingBoxText: {
305 | fontSize: 24,
306 | color: 'white',
307 | fontFamily: Platform.select({ ios: 'Menlo', android: 'monospace' }),
308 | fontWeight: 'bold'
309 | },
310 | dotStyle: {
311 | width: dotSize,
312 | height: dotSize,
313 | borderRadius: dotSize,
314 | backgroundColor: '#fff',
315 | position: 'absolute'
316 | }
317 | });
318 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ### Would you like to support me?
2 |
3 |
4 |
5 | - Paypal: https://paypal.me/catalinmiron
6 |
7 | # React Native Shared Element Transition
8 |
9 | # Run on your device
10 |
11 | Snack: https://snack.expo.io/@catalinmiron/react-native-shared-element-transition
12 |
13 | ### Youtube tutorial
14 |
15 | [](https://youtu.be/42BkfJ-h54E)
16 |
17 | In this lesson we’re going to be building a **Shared Element Transition** in React Native using react-navigation, react-navigation-fluid-transition, React Native and Expo for creating the react-native project.
18 |
19 | - Expo: https://expo.io/
20 | - React Navigation: https://reactnavigation.org
21 | - React Navigation Fluid Transition: https://github.com/fram-x/FluidTransitions
22 | - Inspiration: https://dribbble.com/shots/6520262-Mars
23 | - Planet image: https://pngimg.com/download/61156
24 |
25 | You can find me on:
26 |
27 | - Github: http://github.com/catalinmiron
28 | - Twitter: http://twitter.com/mironcatalin
29 |
--------------------------------------------------------------------------------
/app.json:
--------------------------------------------------------------------------------
1 | {
2 | "expo": {
3 | "name": "react-native-shared-element-transition",
4 | "slug": "react-native-shared-element-transition",
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 | }
--------------------------------------------------------------------------------
/assets/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/catalinmiron/react-native-shared-element-transition/b07fd26484e7ca1f37a6e9d6b1e2b7d7d7ecd08b/assets/icon.png
--------------------------------------------------------------------------------
/assets/mars_planet.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/catalinmiron/react-native-shared-element-transition/b07fd26484e7ca1f37a6e9d6b1e2b7d7d7ecd08b/assets/mars_planet.png
--------------------------------------------------------------------------------
/assets/splash.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/catalinmiron/react-native-shared-element-transition/b07fd26484e7ca1f37a6e9d6b1e2b7d7d7ecd08b/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 |
--------------------------------------------------------------------------------
/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/vector-icons": "^10.0.3",
12 | "expo": "^34.0.1",
13 | "expo-asset": "^6.0.0",
14 | "react": "16.8.3",
15 | "react-dom": "^16.8.6",
16 | "react-native": "https://github.com/expo/react-native/archive/sdk-34.0.0.tar.gz",
17 | "react-native-animatable": "^1.3.2",
18 | "react-native-gesture-handler": "^1.3.0",
19 | "react-native-web": "^0.11.4",
20 | "react-navigation": "^3.11.1",
21 | "react-navigation-fluid-transitions": "^0.3.2"
22 | },
23 | "devDependencies": {
24 | "babel-preset-expo": "^6.0.0"
25 | },
26 | "private": true
27 | }
28 |
--------------------------------------------------------------------------------
/react-native-shared-element-transition.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/catalinmiron/react-native-shared-element-transition/b07fd26484e7ca1f37a6e9d6b1e2b7d7d7ecd08b/react-native-shared-element-transition.png
--------------------------------------------------------------------------------