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: