├── .gitignore ├── LICENSE ├── README.md ├── demo.gif ├── index.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # Directory for instrumented libs generated by jscoverage/JSCover 11 | lib-cov 12 | 13 | # Coverage directory used by tools like istanbul 14 | coverage 15 | 16 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 17 | .grunt 18 | 19 | # node-waf configuration 20 | .lock-wscript 21 | 22 | # Compiled binary addons (http://nodejs.org/api/addons.html) 23 | build/Release 24 | 25 | # Dependency directory 26 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 27 | node_modules 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Olivier Lesnicki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-switcher 2 | 3 | A dead simple zoomable-swiper made popular by the Kardashians' apps. 4 | 5 | ## Installation 6 | 7 | ``` 8 | npm install react-native-switcher --save 9 | ``` 10 | 11 | ## Demo 12 | 13 | ![demo](/demo.gif?raw=true) 14 | 15 | ## Usage 16 | 17 | ```js 18 | var Switcher = require('react-native-switcher'); 19 | var App = React.createClass({ 20 | render() { 21 | return ( 22 | 23 | 24 | 25 | 26 | 27 | ) 28 | } 29 | }) 30 | ``` 31 | -------------------------------------------------------------------------------- /demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/olivierlesnicki/react-native-switcher/c88033ec1777f8c2ced01a383fffa3598b2309fe/demo.gif -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var React = require('react-native'); 2 | var { 3 | Animated, 4 | Dimensions, 5 | PanResponder, 6 | ScrollView, 7 | View, 8 | } = React; 9 | 10 | var { 11 | width, 12 | height 13 | } = Dimensions.get('window'); 14 | 15 | var Slider = React.createClass({ 16 | 17 | getInitialState() { 18 | return { 19 | progress: new Animated.Value(0), 20 | value: 0, 21 | }; 22 | }, 23 | 24 | componentWillMount() { 25 | this.responder = PanResponder.create({ 26 | onMoveShouldSetPanResponder: this.onMoveShouldSetPanResponder, 27 | onPanResponderMove: this.onPanResponderMove, 28 | onPanResponderRelease: this.onPanResponderRelease, 29 | onPanResponderTerminate: this.onPanResponderRelease, 30 | }); 31 | }, 32 | 33 | onMoveShouldSetPanResponder(e, gestureState) { 34 | return Math.abs(gestureState.dx) > Math.abs(gestureState.dy); 35 | }, 36 | 37 | onPanResponderMove(e, gestureState) { 38 | 39 | var dx = gestureState.dx; 40 | var offsetX = dx - this.state.value * this.props.width; 41 | var toValue = -1 * offsetX / this.props.width; 42 | 43 | this.state.progress.setValue(toValue); 44 | 45 | this.props.onMove && this.props.onMove(toValue); 46 | 47 | }, 48 | 49 | onPanResponderRelease(e, gestureState) { 50 | 51 | var rx = gestureState.dx / this.props.width, 52 | vx = gestureState.vx, 53 | toValue = this.state.value; 54 | 55 | if (rx < -0.5 || (rx < 0 && vx <=0.5)) { 56 | toValue = toValue + 1; 57 | } else if (rx >= 0.5 || (rx > 0 && vx >=0.5)) { 58 | toValue = toValue - 1; 59 | } 60 | 61 | toValue = Math.max(0, Math.min(toValue, this.props.size - 1)); 62 | 63 | this.setState({ 64 | value: toValue, 65 | }); 66 | 67 | this.props.onRelease && this.props.onRelease(toValue); 68 | 69 | }, 70 | 71 | render() { 72 | return ( 73 | 82 | {this.props.children} 83 | 84 | ); 85 | } 86 | 87 | }); 88 | 89 | var Zoomer = React.createClass({ 90 | 91 | getInitialState() { 92 | return { 93 | progress: new Animated.Value(0), 94 | value: 0, 95 | }; 96 | }, 97 | 98 | componentWillMount() { 99 | this.responder = PanResponder.create({ 100 | onMoveShouldSetPanResponder: this.onMoveShouldSetPanResponder, 101 | onPanResponderMove: this.onPanResponderMove, 102 | onPanResponderRelease: this.onPanResponderRelease, 103 | onPanResponderTerminate: this.onPanResponderRelease, 104 | }); 105 | }, 106 | 107 | onMoveShouldSetPanResponder(e, gestureState) { 108 | if (Math.abs(gestureState.dy) > Math.abs(gestureState.dx)) { 109 | if (gestureState.dy > 0 && this.state.value === 0 || this.state.value === 1 && gestureState.dy < 0) { 110 | return true; 111 | } 112 | } 113 | }, 114 | 115 | onPanResponderMove(e, gestureState) { 116 | 117 | var dy = gestureState.dy; 118 | var offsetY = dy + this.state.value * (this.props.height / 2); 119 | var toValue = offsetY / (this.props.height / 2); 120 | 121 | this.state.progress.setValue(toValue); 122 | 123 | this.props.onMove && this.props.onMove(toValue); 124 | 125 | }, 126 | 127 | onPanResponderRelease(e, gestureState) { 128 | 129 | var ry = gestureState.dy / (this.props.height / 2), 130 | vy = gestureState.vy, 131 | toValue = this.state.value; 132 | 133 | if (ry < -0.5 || (ry < 0 && vy <=0.5)) { 134 | toValue = 0; 135 | } else if (ry >= 0.5 || (ry > 0 && vy >=0.5)) { 136 | toValue = 1; 137 | } 138 | 139 | this.setState({ 140 | value: toValue, 141 | }); 142 | 143 | this.props.onRelease && this.props.onRelease(toValue); 144 | 145 | }, 146 | 147 | render() { 148 | return ( 149 | 158 | {this.props.children} 159 | 160 | ); 161 | }, 162 | 163 | }); 164 | 165 | var Switcher = React.createClass({ 166 | 167 | getInitialState() { 168 | return { 169 | height: 0, 170 | width: 0, 171 | zoom: new Animated.Value(0), 172 | slide: new Animated.Value(0), 173 | }; 174 | }, 175 | 176 | onZoomMove(toValue) { 177 | this.state.zoom.setValue(toValue); 178 | }, 179 | 180 | onZoomRelease(toValue) { 181 | Animated.spring(this.state.zoom, { 182 | toValue: toValue, 183 | friction: 7, 184 | tension: 70, 185 | }).start(); 186 | }, 187 | 188 | onSlideMove(toValue) { 189 | this.state.slide.setValue(toValue); 190 | }, 191 | 192 | onSlideRelease(toValue) { 193 | Animated.spring(this.state.slide, { 194 | toValue: toValue, 195 | friction: 10, 196 | tension: 50, 197 | }).start(); 198 | }, 199 | 200 | onLayout(e) { 201 | var {height, width} = e.nativeEvent.layout; 202 | this.setState({height, width}); 203 | }, 204 | 205 | render() { 206 | return ( 207 | 213 | 223 | 233 | 240 | {this.props.children} 241 | 242 | 243 | 244 | 245 | ); 246 | }, 247 | 248 | }); 249 | 250 | module.exports = Switcher; 251 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-switcher", 3 | "version": "0.1.0", 4 | "description": "A dead simple zoomable-swiper made popular by the Kardashians' apps", 5 | "main": "index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "git+https://github.com/olivierlesnicki/react-native-switcher.git" 9 | }, 10 | "author": "", 11 | "license": "MIT", 12 | "bugs": { 13 | "url": "https://github.com/olivierlesnicki/react-native-switcher/issues" 14 | }, 15 | "homepage": "https://github.com/olivierlesnicki/react-native-switcher#readme" 16 | } 17 | --------------------------------------------------------------------------------