├── .gitignore ├── package.json ├── LICENSE ├── README.md └── index.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "react-native-swipe-flip", 3 | "version": "1.0.2", 4 | "description": "A React Native view component that flips front-to-back in response to swipe gestures.", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "dependencies": { 10 | "react-native-simple-gesture": "0.0.2" 11 | }, 12 | "repository": { 13 | "type": "git", 14 | "url": "git+https://github.com/aprct/react-native-swipe-flip.git" 15 | }, 16 | "keywords": [ 17 | "react-native", 18 | "ios", 19 | "android", 20 | "flip", 21 | "swipe", 22 | "view", 23 | "swipeflip" 24 | ], 25 | "author": "Fei Li", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/aprct/react-native-swipe-flip/issues" 29 | }, 30 | "homepage": "https://github.com/aprct/react-native-swipe-flip#readme" 31 | } 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 aprct.io 4 | 5 | This Software uses the following Software: 6 | react-native-flip-view - Copyright (c) 2015 DispatcherInc 7 | react-native-simple-gesture - Copyright (c) 2016 Christopher 8 | 9 | Permission is hereby granted, free of charge, to any person obtaining a copy 10 | of this software and associated documentation files (the "Software"), to deal 11 | in the Software without restriction, including without limitation the rights 12 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 | copies of the Software, and to permit persons to whom the Software is 14 | furnished to do so, subject to the following conditions: 15 | 16 | The above copyright notice and this permission notice shall be included in all 17 | copies or substantial portions of the Software. 18 | 19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # react-native-swipe-flip 2 | A React Native view component that flips front-to-back in response to swipe gestures. 3 | 4 | ## Installation 5 | 6 | ```sh 7 | npm install react-native-swipe-flip 8 | ``` 9 | 10 | ## Demo 11 | ![swipeflipdemo](http://i.imgur.com/FJ9YPip.gif) 12 | 13 | ## Interaction 14 | The view will respond to the following swipe directions: 15 | * `'up'` 16 | * `'down'` 17 | * `'left'` 18 | * `'right'` 19 | 20 | Programmatically flip the card by accessing the component via ref: 21 | ```js 22 | { this.swipeFlip = c; }} 23 | style={{ flex: 1 }} 24 | front={ this._renderFront() } 25 | back={ this._renderBack() } 26 | onFlipped={(val) => { console.log('Flipped: ' + val); }} 27 | flipEasing={ Easing.out(Easing.ease) } 28 | flipDuration={ 500 } 29 | perspective={ 1000 } /> 30 | ``` 31 | 32 | Now you can call the `flip(swipeDirection)` method and pass a swipe direction as the parameter: 33 | `this.swipeFlip.flip('left');` 34 | 35 | ## Example 36 | 37 | ```js 38 | 'use strict'; 39 | 40 | import React from 'react'; 41 | import { View, Text, Easing } from 'react-native'; 42 | 43 | import SwipeFlip from 'react-native-swipe-flip'; 44 | 45 | export default class Playground extends React.Component { 46 | 47 | constructor(props) { 48 | super(props); 49 | } 50 | 51 | render() { 52 | return ( 53 | 54 | { console.log('Flipped: ' + val); }} 58 | flipEasing={ Easing.out(Easing.ease) } 59 | flipDuration={ 500 } 60 | perspective={ 1000 } /> 61 | 62 | ) 63 | } 64 | 65 | _renderFront() { 66 | return ( 67 | 68 | 69 | 70 | { `FRONT\nSwipe to flip!` } 71 | 72 | 73 | 74 | ); 75 | } 76 | 77 | _renderBack() { 78 | return ( 79 | 80 | 81 | 82 | { `BACK\nSwipe to flip!` } 83 | 84 | 85 | 86 | ); 87 | } 88 | } 89 | ``` 90 | 91 | .babelrc: 92 | ```js 93 | { 94 | "retainLines": true, 95 | "whitelist": [ 96 | "es6.arrowFunctions", 97 | "es6.blockScoping", 98 | "es6.classes", 99 | "es6.constants", 100 | "es6.destructuring", 101 | "es6.modules", 102 | "es6.parameters", 103 | "es6.properties.computed", 104 | "es6.properties.shorthand", 105 | "es6.spread", 106 | "es6.templateLiterals", 107 | "es7.asyncFunctions", 108 | "es7.trailingFunctionCommas", 109 | "es7.objectRestSpread", 110 | "es7.classProperties", 111 | "es7.decorators", 112 | "flow", 113 | "react", 114 | "react.displayName", 115 | "regenerator" 116 | ] 117 | } 118 | ``` 119 | 120 | # Thanks 121 | Big thanks to [@kevinstumpf](https://github.com/kevinstumpf) for [React Native Flip View](https://github.com/kevinstumpf/react-native-flip-view) and [@christopherabouabdo](https://github.com/christopherabouabdo) for [React Native Simple Gesture](https://github.com/christopherabouabdo/react-native-simple-gesture). 122 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | import React from 'react'; 4 | const { Component, PropTypes } = React; 5 | 6 | import ReactNative from 'react-native'; 7 | const { View, Easing, StyleSheet, Animated, PanResponder } = ReactNative; 8 | 9 | import SimpleGesture from 'react-native-simple-gesture'; 10 | 11 | // Not exported 12 | var rotation = { 13 | frontRotation: 0, 14 | backRotation: 0.5 15 | }; 16 | var swipeDirection = null; 17 | 18 | class SwipeFlip extends Component { 19 | constructor(props) { 20 | super(props); 21 | 22 | const frontRotationAnimatedValue = new Animated.Value(rotation.frontRotation); 23 | const backRotationAnimatedValue = new Animated.Value(rotation.backRotation); 24 | 25 | const interpolationConfig = { inputRange: [0, 1], outputRange: ['0deg', '360deg'] }; 26 | const frontRotation = frontRotationAnimatedValue.interpolate(interpolationConfig); 27 | const backRotation = backRotationAnimatedValue.interpolate(interpolationConfig); 28 | 29 | this.state = { 30 | frontRotationAnimatedValue, 31 | backRotationAnimatedValue, 32 | frontRotation, 33 | backRotation, 34 | isFlipped: props.isFlipped, 35 | rotateProperty: 'rotateY' 36 | }; 37 | } 38 | 39 | componentWillMount() { 40 | this._panResponder = PanResponder.create({ 41 | onStartShouldSetPanResponder: (evt, gestureState) => true, 42 | onStartShouldSetPanResponderCapture: (evt, gestureState) => true, 43 | onMoveShouldSetPanResponder: (evt, gestureState) => true, 44 | onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, 45 | onPanResponderGrant: (evt, gestureState) => { 46 | // do stuff on start -- unused 47 | }, 48 | onPanResponderMove: (evt, gestureState) => { 49 | // do stuff on move -- unused 50 | }, 51 | onPanResponderTerminationRequest: (evt, gestureState) => true, 52 | onPanResponderRelease: (evt, gestureState) => { this._onSwipe(evt, gestureState); }, 53 | onPanResponderTerminate: (evt, gestureState) => { 54 | }, 55 | onShouldBlockNativeResponder: (evt, gestureState) => { 56 | return true; 57 | } 58 | }); 59 | } 60 | 61 | _onSwipe(evt, gestureState) { 62 | const sgs = new SimpleGesture(evt, gestureState); 63 | 64 | swipeDirection = sgs.isSwipeLeft() ? 'left' : 65 | sgs.isSwipeRight() ? 'right' : 66 | sgs.isSwipeUp() ? 'up' : 67 | sgs.isSwipeDown() ? 'down' : null; 68 | 69 | if(swipeDirection) { 70 | this.setState({ rotateProperty: (swipeDirection === 'left' || swipeDirection === 'right') ? 'rotateY' : 'rotateX' }); 71 | this.flip(swipeDirection); 72 | } 73 | } 74 | 75 | _getTargetRenderState(swipeDirection) { 76 | rotation = swipeDirection ? { 77 | frontRotation: (swipeDirection === 'right' || swipeDirection === 'up') ? rotation.frontRotation + 0.5 : rotation.frontRotation - 0.5, 78 | backRotation: (swipeDirection === 'right' || swipeDirection === 'up') ? rotation.backRotation + 0.5 : rotation.backRotation - 0.5 79 | } : rotation; 80 | 81 | return rotation; 82 | } 83 | 84 | render() { 85 | return ( 86 | 87 | 89 | { this.props.front } 90 | 91 | 93 | { this.props.back } 94 | 95 | 96 | ); 97 | } 98 | 99 | flip(swipeDirection) { 100 | this.props.onFlip(); 101 | 102 | if( ['up', 'down', 'left', 'right'].indexOf(swipeDirection) === -1 ) { 103 | swipeDirection = 'right'; 104 | } 105 | 106 | const nextIsFlipped = !this.state.isFlipped; 107 | 108 | const { frontRotation, backRotation } = this._getTargetRenderState(swipeDirection); 109 | 110 | setImmediate(() => { 111 | requestAnimationFrame(() => { 112 | Animated.parallel([ 113 | this._animateValue(this.state.frontRotationAnimatedValue, frontRotation, this.props.flipEasing), 114 | this._animateValue(this.state.backRotationAnimatedValue, backRotation, this.props.flipEasing) 115 | ]).start(k => { 116 | if (!k.finished) { return; } 117 | 118 | this.setState({ isFlipped: nextIsFlipped }); 119 | this.props.onFlipped(nextIsFlipped); 120 | }); 121 | }); 122 | }); 123 | } 124 | 125 | _animateValue(animatedValue, toValue, easing) { 126 | return Animated.timing( 127 | animatedValue, 128 | { 129 | toValue: toValue, 130 | duration: this.props.flipDuration, 131 | easing: easing 132 | } 133 | ); 134 | } 135 | } 136 | 137 | SwipeFlip.defaultProps = { 138 | style: {}, 139 | flipDuration: 500, 140 | flipEasing: Easing.out(Easing.ease), 141 | perspective: 1000, 142 | onFlip: () => {}, 143 | onFlipped: () => {} 144 | }; 145 | 146 | SwipeFlip.propTypes = { 147 | style: View.propTypes.style, 148 | flipDuration: PropTypes.number, 149 | flipEasing: PropTypes.func, 150 | front: PropTypes.object, 151 | back: PropTypes.object, 152 | perspective: PropTypes.number, 153 | onFlip: PropTypes.func, 154 | onFlipped: PropTypes.func 155 | }; 156 | 157 | const styles = StyleSheet.create({ 158 | flippableView: { 159 | position: 'absolute', 160 | left: 0, 161 | top: 0, 162 | right: 0, 163 | bottom: 0, 164 | backfaceVisibility: 'hidden' 165 | } 166 | }); 167 | 168 | module.exports = SwipeFlip; --------------------------------------------------------------------------------