Tinder is one of the most famous locally people search application used to find partners near by you. Tinder has its unique set of CardViews structure used with Swipe Left and Swipe Right used to Send and Reject partner profiles. So in this tutorial we would going to Create Swipeable CardView like Tinder in iOS Android react native application fully explained example tutorial. We are using PanResponder component in our tutorial to detect Swipe left and Right on screen and also applying the Gesture Effect, So let’s get started 🙂 .
Live screenshot of App :
Contents in this project Swipeable CardView like Tinder in iOS Android React Native App Example Tutorial:
1. Import Platform, StyleSheet, View, Text, Dimensions, Animated and PanResponder component in your project.
1 2 3 |
import React, { Component } from 'react'; import { Platform, StyleSheet, View, Text, Dimensions, Animated, PanResponder } from 'react-native'; |
2. Create a constant named as SCREEN_WIDTH and store the current device screen width in dimensions.
1 |
const SCREEN_WIDTH = Dimensions.get('window').width; |
3. Create a New class named as SwipeableCardView in your App.js file, This would be our Custom Animated Swipeable CardView class.
1 2 3 4 5 6 |
class SwipeableCardView extends Component<{}> { } |
4. Create constructor() inside SwipeableCardView class.
this.panResponder : Used to handle Pan Responder.
Xposition : Hold the animated value of X-Axis.
RightText : Used to Show and Hide the Right Swipe Text.
LeftText : Used to show and hide the Left Swipe Text.
this.CardView_Opacity : Setting up the animated opacity value.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
constructor() { super(); this.panResponder; this.state = { Xposition: new Animated.Value(0), RightText: false, LeftText: false, } this.CardView_Opacity = new Animated.Value(1); } |
5. Create componentWillMount() inbuilt method in SwipeableCardView class. Inside this function we would implement the PanResponder.create() method to control the gesture effect.
PanResponder.create() : Used to create Pan Responder method.
onPanResponderMove: (evt, gestureState) : Changing the Xposition State value while moving(dragging) the Card View.
onPanResponderRelease: (evt, gestureState) : Updating the View and firing the Animation to Move CardView.
Note: We have 2 State named as RightText and LeftText which is used to show and hide the Left Swipe and Right Swipe Text on CardView on Card Swipe event.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
componentWillMount() { this.panResponder = PanResponder.create( { onStartShouldSetPanResponder: (evt, gestureState) => false, onMoveShouldSetPanResponder: (evt, gestureState) => true, onStartShouldSetPanResponderCapture: (evt, gestureState) => false, onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, onPanResponderMove: (evt, gestureState) => { this.state.Xposition.setValue(gestureState.dx); if( gestureState.dx > SCREEN_WIDTH - 250 ) { this.setState({ RightText: true, LeftText: false }); } else if( gestureState.dx < -SCREEN_WIDTH + 250 ) { this.setState({ LeftText: true, RightText: false }); } }, onPanResponderRelease: (evt, gestureState) => { if( gestureState.dx < SCREEN_WIDTH - 150 && gestureState.dx > -SCREEN_WIDTH + 150 ) { this.setState({ LeftText: false, RightText: false }); Animated.spring( this.state.Xposition, { toValue: 0, speed: 5, bounciness: 10, }, { useNativeDriver: true }).start(); } else if( gestureState.dx > SCREEN_WIDTH - 150 ) { Animated.parallel( [ Animated.timing( this.state.Xposition, { toValue: SCREEN_WIDTH, duration: 200 }), Animated.timing( this.CardView_Opacity, { toValue: 0, duration: 200 }) ], { useNativeDriver: true }).start(() => { this.setState({ LeftText: false, RightText: false }, () => { this.props.removeCardView(); }); }); } else if( gestureState.dx < -SCREEN_WIDTH + 150 ) { Animated.parallel( [ Animated.timing( this.state.Xposition, { toValue: -SCREEN_WIDTH, duration: 200 }), Animated.timing( this.CardView_Opacity, { toValue: 0, duration: 200 }) ], { useNativeDriver: true }).start(() => { this.setState({ LeftText: false, RightText: false }, () => { this.props.removeCardView(); }); }); } } }); } |
6. Create rotateCard interpolate with input and output range of animation in render’s block of SwipeableCardView class.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
render() { const rotateCard = this.state.Xposition.interpolate( { inputRange: [-200, 0, 200], outputRange: ['-20deg', '0deg', '20deg'], }); return( ); } |
7. Create a Animated View inside render’s return block of SwipeableCardView class. This View is used to dynamically Show all the cardViews with dragging and moving animation.
It will show us the CardView title we will pass from our Main class and if user swipes left the it will show the Left Swipe text and if the user swipes right then it will show us the Right Swipe text using States.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
render() { const rotateCard = this.state.Xposition.interpolate( { inputRange: [-200, 0, 200], outputRange: ['-20deg', '0deg', '20deg'], }); return( <Animated.View {...this.panResponder.panHandlers} style = {[ styles.cardView_Style, { backgroundColor: this.props.item.backgroundColor, opacity: this.CardView_Opacity, transform: [{ translateX: this.state.Xposition }, { rotate: rotateCard }]} ]}> <Text style = { styles.CardView_Title }> { this.props.item.cardView_Title } </Text> { ( this.state.LeftText ) ? (<Text style = { styles.Left_Text_Style }> Left Swipe </Text>) : null } { ( this.state.RightText ) ? (<Text style = { styles.Right_Text_Style }> Right Swipe </Text>) : null } </Animated.View> ); } |
8. Complete Source Code of SwipeableCardView class.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 |
class SwipeableCardView extends Component<{}> { constructor() { super(); this.panResponder; this.state = { Xposition: new Animated.Value(0), RightText: false, LeftText: false, } this.CardView_Opacity = new Animated.Value(1); } componentWillMount() { this.panResponder = PanResponder.create( { onStartShouldSetPanResponder: (evt, gestureState) => false, onMoveShouldSetPanResponder: (evt, gestureState) => true, onStartShouldSetPanResponderCapture: (evt, gestureState) => false, onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, onPanResponderMove: (evt, gestureState) => { this.state.Xposition.setValue(gestureState.dx); if( gestureState.dx > SCREEN_WIDTH - 250 ) { this.setState({ RightText: true, LeftText: false }); } else if( gestureState.dx < -SCREEN_WIDTH + 250 ) { this.setState({ LeftText: true, RightText: false }); } }, onPanResponderRelease: (evt, gestureState) => { if( gestureState.dx < SCREEN_WIDTH - 150 && gestureState.dx > -SCREEN_WIDTH + 150 ) { this.setState({ LeftText: false, RightText: false }); Animated.spring( this.state.Xposition, { toValue: 0, speed: 5, bounciness: 10, }, { useNativeDriver: true }).start(); } else if( gestureState.dx > SCREEN_WIDTH - 150 ) { Animated.parallel( [ Animated.timing( this.state.Xposition, { toValue: SCREEN_WIDTH, duration: 200 }), Animated.timing( this.CardView_Opacity, { toValue: 0, duration: 200 }) ], { useNativeDriver: true }).start(() => { this.setState({ LeftText: false, RightText: false }, () => { this.props.removeCardView(); }); }); } else if( gestureState.dx < -SCREEN_WIDTH + 150 ) { Animated.parallel( [ Animated.timing( this.state.Xposition, { toValue: -SCREEN_WIDTH, duration: 200 }), Animated.timing( this.CardView_Opacity, { toValue: 0, duration: 200 }) ], { useNativeDriver: true }).start(() => { this.setState({ LeftText: false, RightText: false }, () => { this.props.removeCardView(); }); }); } } }); } render() { const rotateCard = this.state.Xposition.interpolate( { inputRange: [-200, 0, 200], outputRange: ['-20deg', '0deg', '20deg'], }); return( <Animated.View {...this.panResponder.panHandlers} style = {[ styles.cardView_Style, { backgroundColor: this.props.item.backgroundColor, opacity: this.CardView_Opacity, transform: [{ translateX: this.state.Xposition }, { rotate: rotateCard }]} ]}> <Text style = { styles.CardView_Title }> { this.props.item.cardView_Title } </Text> { ( this.state.LeftText ) ? (<Text style = { styles.Left_Text_Style }> Left Swipe </Text>) : null } { ( this.state.RightText ) ? (<Text style = { styles.Right_Text_Style }> Right Swipe </Text>) : null } </Animated.View> ); } } |
9. Create main class in our project named as MyApp, this class would be our main Default Export class.
1 2 3 4 5 |
export default class MyApp extends Component<{}> { } |
10. Create constructor() in your Main class and inside this constructor we would make a State array named as Sample_CardView_Items_Array with 5 different items, these items would show inside the CardViews. The No_More_CardView State is used to show the No More CardViews Found text after swiping all the cards.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
constructor() { super(); this.state = { Sample_CardView_Items_Array: [ { id: '1', cardView_Title: 'CardView 1', backgroundColor: '#4CAF50' }, { id: '2', cardView_Title: 'CardView 2', backgroundColor: '#607D8B' }, { id: '3', cardView_Title: 'CardView 3', backgroundColor: '#9C27B0' }, { id: '4', cardView_Title: 'CardView 4', backgroundColor: '#00BCD4' }, { id: '5', cardView_Title: 'CardView 5', backgroundColor: '#FFC107' }, ], No_More_CardView: false }; } |
11. Create componentDidMount() method in your main class.
1 2 3 4 5 6 7 8 9 |
componentDidMount() { this.setState({ Sample_CardView_Items_Array: this.state.Sample_CardView_Items_Array.reverse() }); if( this.state.Sample_CardView_Items_Array.length == 0 ) { this.setState({ No_More_CardView: true }); } } |
12. Create a function named as removeCardView() in your main class, Using this function we would remove the swiped Card from array and show the next item on screen.
1 2 3 4 5 6 7 8 9 10 11 12 |
removeCardView =(id)=> { this.state.Sample_CardView_Items_Array.splice( this.state.Sample_CardView_Items_Array.findIndex( x => x.id == id ), 1 ); this.setState({ Sample_CardView_Items_Array: this.state.Sample_CardView_Items_Array }, () => { if( this.state.Sample_CardView_Items_Array.length == 0 ) { this.setState({ No_More_CardView: true }); } }); } |
13. Create render’s return block in your main class. Now we would create SwipeableCardView component and using array looping we would start printing CardView, we would also show a text No More CardViews Found if all the card successfully removed from screen.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
render() { return( <View style = { styles.MainContainer }> { this.state.Sample_CardView_Items_Array.map(( item, key ) => ( <SwipeableCardView key = { key } item = { item } removeCardView = { this.removeCardView.bind( this, item.id ) }/> )) } { ( this.state.No_More_CardView ) ? ( <Text style = {{ fontSize: 22, color: '#000' }}>No More CardViews Found.</Text> ) : null } </View> ); } |
14. Creating Style.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 |
const styles = StyleSheet.create( { MainContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingTop: ( Platform.OS === 'ios' ) ? 20 : 0 }, cardView_Style: { width: '75%', height: '45%', justifyContent: 'center', alignItems: 'center', position: 'absolute', borderRadius: 7 }, CardView_Title: { color: '#fff', fontSize: 24 }, Left_Text_Style: { top: 22, right: 32, position: 'absolute', color: '#fff', fontSize: 20, fontWeight: 'bold', backgroundColor: 'transparent' }, Right_Text_Style: { top: 22, left: 32, position: 'absolute', color: '#fff', fontSize: 20, fontWeight: 'bold', backgroundColor: 'transparent' } }); |
15. Complete source code for App.js File :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 |
import React, { Component } from 'react'; import { Platform, StyleSheet, View, Text, Dimensions, Animated, PanResponder } from 'react-native'; const SCREEN_WIDTH = Dimensions.get('window').width; class SwipeableCardView extends Component<{}> { constructor() { super(); this.panResponder; this.state = { Xposition: new Animated.Value(0), RightText: false, LeftText: false, } this.CardView_Opacity = new Animated.Value(1); } componentWillMount() { this.panResponder = PanResponder.create( { onStartShouldSetPanResponder: (evt, gestureState) => false, onMoveShouldSetPanResponder: (evt, gestureState) => true, onStartShouldSetPanResponderCapture: (evt, gestureState) => false, onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, onPanResponderMove: (evt, gestureState) => { this.state.Xposition.setValue(gestureState.dx); if( gestureState.dx > SCREEN_WIDTH - 250 ) { this.setState({ RightText: true, LeftText: false }); } else if( gestureState.dx < -SCREEN_WIDTH + 250 ) { this.setState({ LeftText: true, RightText: false }); } }, onPanResponderRelease: (evt, gestureState) => { if( gestureState.dx < SCREEN_WIDTH - 150 && gestureState.dx > -SCREEN_WIDTH + 150 ) { this.setState({ LeftText: false, RightText: false }); Animated.spring( this.state.Xposition, { toValue: 0, speed: 5, bounciness: 10, }, { useNativeDriver: true }).start(); } else if( gestureState.dx > SCREEN_WIDTH - 150 ) { Animated.parallel( [ Animated.timing( this.state.Xposition, { toValue: SCREEN_WIDTH, duration: 200 }), Animated.timing( this.CardView_Opacity, { toValue: 0, duration: 200 }) ], { useNativeDriver: true }).start(() => { this.setState({ LeftText: false, RightText: false }, () => { this.props.removeCardView(); }); }); } else if( gestureState.dx < -SCREEN_WIDTH + 150 ) { Animated.parallel( [ Animated.timing( this.state.Xposition, { toValue: -SCREEN_WIDTH, duration: 200 }), Animated.timing( this.CardView_Opacity, { toValue: 0, duration: 200 }) ], { useNativeDriver: true }).start(() => { this.setState({ LeftText: false, RightText: false }, () => { this.props.removeCardView(); }); }); } } }); } render() { const rotateCard = this.state.Xposition.interpolate( { inputRange: [-200, 0, 200], outputRange: ['-20deg', '0deg', '20deg'], }); return( <Animated.View {...this.panResponder.panHandlers} style = {[ styles.cardView_Style, { backgroundColor: this.props.item.backgroundColor, opacity: this.CardView_Opacity, transform: [{ translateX: this.state.Xposition }, { rotate: rotateCard }]} ]}> <Text style = { styles.CardView_Title }> { this.props.item.cardView_Title } </Text> { ( this.state.LeftText ) ? (<Text style = { styles.Left_Text_Style }> Left Swipe </Text>) : null } { ( this.state.RightText ) ? (<Text style = { styles.Right_Text_Style }> Right Swipe </Text>) : null } </Animated.View> ); } } export default class MyApp extends Component<{}> { constructor() { super(); this.state = { Sample_CardView_Items_Array: [ { id: '1', cardView_Title: 'CardView 1', backgroundColor: '#4CAF50' }, { id: '2', cardView_Title: 'CardView 2', backgroundColor: '#607D8B' }, { id: '3', cardView_Title: 'CardView 3', backgroundColor: '#9C27B0' }, { id: '4', cardView_Title: 'CardView 4', backgroundColor: '#00BCD4' }, { id: '5', cardView_Title: 'CardView 5', backgroundColor: '#FFC107' }, ], No_More_CardView: false }; } componentDidMount() { this.setState({ Sample_CardView_Items_Array: this.state.Sample_CardView_Items_Array.reverse() }); if( this.state.Sample_CardView_Items_Array.length == 0 ) { this.setState({ No_More_CardView: true }); } } removeCardView =(id)=> { this.state.Sample_CardView_Items_Array.splice( this.state.Sample_CardView_Items_Array.findIndex( x => x.id == id ), 1 ); this.setState({ Sample_CardView_Items_Array: this.state.Sample_CardView_Items_Array }, () => { if( this.state.Sample_CardView_Items_Array.length == 0 ) { this.setState({ No_More_CardView: true }); } }); } render() { return( <View style = { styles.MainContainer }> { this.state.Sample_CardView_Items_Array.map(( item, key ) => ( <SwipeableCardView key = { key } item = { item } removeCardView = { this.removeCardView.bind( this, item.id ) }/> )) } { ( this.state.No_More_CardView ) ? ( <Text style = {{ fontSize: 22, color: '#000' }}>No More CardViews Found.</Text> ) : null } </View> ); } } const styles = StyleSheet.create( { MainContainer: { flex: 1, justifyContent: 'center', alignItems: 'center', paddingTop: ( Platform.OS === 'ios' ) ? 20 : 0 }, cardView_Style: { width: '75%', height: '45%', justifyContent: 'center', alignItems: 'center', position: 'absolute', borderRadius: 7 }, CardView_Title: { color: '#fff', fontSize: 24 }, Left_Text_Style: { top: 22, right: 32, position: 'absolute', color: '#fff', fontSize: 20, fontWeight: 'bold', backgroundColor: 'transparent' }, Right_Text_Style: { top: 22, left: 32, position: 'absolute', color: '#fff', fontSize: 20, fontWeight: 'bold', backgroundColor: 'transparent' } }); |
Screenshots in Android device:
nice tutorial……please make one tutorial for sqlite
Sure Ajay in couple of days i will make tutorial on both SQLite and Realm 🙂 .
Thank you for your valuable post
Welcome Manoj 🙂 .
Great post! Do you have advice on how to add images?
Yes sully, you can search on our website for more tutorials.
getting an error as cannot read proper ‘panHandlers’ of undefined