ScrollView in react native is one of the mostly used component after FlatList. ScrollView can render different types of component with different properties. In this tutorial we would Dynamically Add Remove Component using Animation in ScrollView component. We would add Views in scrollView on application run time and also provide the functionality to our user so he can remove them dynamically with animation. We are using the translateX animation type so the views comes from left side. If user decided to remove the views then they would remove by going into right side of mobile screen. So let’s get started .
Before going further i would like to show you the live screenshot of this application. See the screenshot below.
Live Screenshot:
Contents in this project Dynamically Add Remove Component using Animation in ScrollView in React Native Android iOS Tutorial:
1. Import Animated, Dimensions, Image, LayoutAnimation, Platform, ScrollView, StyleSheet, Text, TouchableOpacity, UIManager and View component in your project’s App.js file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
import React, { Component } from ‘react’;
import {
Animated,
Dimensions,
Image,
LayoutAnimation,
Platform,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
UIManager,
View
} from ‘react-native’;
|
2. Create a constant variable named as width and get the device width dynamically using Dimensions.get(‘window’).width method. We would use this width variable to manage the width of device to know how inside we have to put the view with animation.
1
|
const width = Dimensions.get(‘window’).width;
|
3. Create a class named as Animated_Item in your App.js file. We are creating our own animation component in this tutorial.
1
2
3
4
5
|
class Animated_Item extends Component {
}
|
4. Creating the constructor() method in Animated_Item class. First of all we will set the Animated value to zero and store it in this.animatedValue. Now we would use the LayoutAnimation. LayoutAnimation usages when user delete an item from ScrollView then to reset the ScrollView on item delete with animation.
1
2
3
4
5
6
7
8
9
10
|
constructor() {
super();
this.animatedValue = new Animated.Value(0);
if (Platform.OS === ‘android’) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
}
|
5. Create shouldComponentUpdate() inbuilt method with 2 parameters.
- shouldComponentUpdate() method is used to increase performance of react native mobile application.
- It returns value in true, false form.
- The use of shouldComponentUpdate() in our app is very important. It usages re-render disable. For example : If we have 10 items in our scrollView and we would add another new item. Then by default the app would render all 11 items again which would decrease the app performance. But by using shouldComponentUpdate() method we would only render the 11th item. This would increase the app performance.
1
2
3
4
5
6
|
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.item.id !== this.props.item.id) {
return true;
}
return false;
}
|
6. Create componentDidMount() method in Animated_Item class.
In this method we would simply set the timing of animation perform time. How longer the animation take time to complete. You can increase or decrease the timing here as your requirement. We are also using the this.props.afterAnimationComplete() method which would receive a prop from parent component.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
componentDidMount() {
Animated.timing(
this.animatedValue,
{
toValue: 0.5,
duration: 510,
useNativeDriver: true
}
).start(() => {
this.props.afterAnimationComplete();
});
}
|
7. Create a function named as deleteItem() in Animated_Item class.
This function would remove the current selected item. We have defined the animation execution timing in it.
1
2
3
4
5
6
7
8
9
10
11
12
|
deleteItem = () => {
Animated.timing(
this.animatedValue,
{
toValue: 1,
duration: 510,
useNativeDriver: true
}
).start(() => {
this.props.deleteItem(this.props.item.id);
});
}
|
8. Creating code for render’s block in in Animated_Item class.
- translate_Animation_Object : Used for moving animation with width constant.
- opacity_Animation_Object : Used to increase and decrease the opacity of View on removing and adding time.
- u00D7 : Creating X cross icon with UNI HTML code.
- translateX : We are using the X-Type animation so the views will come from left side to right side. You can also use the translateY animation if you want to put view from top to bottom .
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 translate_Animation_Object = this.animatedValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [–width, 0, width]
});
const opacity_Animation_Object = this.animatedValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [0, 1, 0]
});
return (
<Animated.View style={[
styles.singleItemView, {
transform: [{ translateX: translate_Animation_Object }],
opacity: opacity_Animation_Object
}]}>
<Text style={styles.singleItemText}>
Item {this.props.item.text}
</Text>
<TouchableOpacity
style={styles.deleteButton}
onPress={this.deleteItem}>
<Text style={styles.removeIcon}>{‘u00D7’}</Text>
</TouchableOpacity>
</Animated.View>
);
}
|
9. Complete source code for in Animated_Item 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
|
class Animated_Item extends Component {
constructor() {
super();
this.animatedValue = new Animated.Value(0);
if (Platform.OS === ‘android’) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
}
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.item.id !== this.props.item.id) {
return true;
}
return false;
}
componentDidMount() {
Animated.timing(
this.animatedValue,
{
toValue: 0.5,
duration: 510,
useNativeDriver: true
}
).start(() => {
this.props.afterAnimationComplete();
});
}
deleteItem = () => {
Animated.timing(
this.animatedValue,
{
toValue: 1,
duration: 510,
useNativeDriver: true
}
).start(() => {
this.props.deleteItem(this.props.item.id);
});
}
render() {
const translate_Animation_Object = this.animatedValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [–width, 0, width]
});
const opacity_Animation_Object = this.animatedValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [0, 1, 0]
});
return (
<Animated.View style={[
styles.singleItemView, {
transform: [{ translateX: translate_Animation_Object }],
opacity: opacity_Animation_Object
}]}>
<Text style={styles.singleItemText}>
Item {this.props.item.text}
</Text>
<TouchableOpacity
style={styles.deleteButton}
onPress={this.deleteItem}>
<Text style={styles.removeIcon}>{‘u00D7’}</Text>
</TouchableOpacity>
</Animated.View>
);
}
}
|
Now its time to code for your app’s main App.js parent App export default class.
10. Create a class named as App with export default syntax. This class would be the default calling parent class on application execution time.
1
2
3
4
5
|
export default class App extends Component {
}
|
11. Create constructor() in App class.
valueArray : State is used to manage all the items of array.
disabled : State disabled the Add item floating button when the animation is performing and then enable the button again on animation complete.
1
2
3
4
5
6
7
|
constructor() {
super();
this.state = { valueArray: [], disabled: false }
this.addNewElement = false;
this.index = 0;
}
|
12. Create a function named as afterAnimationComplete() in App class. In this function we would increase the current item index number to +1 then enable the button with disabled false state.
1
2
3
4
|
afterAnimationComplete = () => {
this.index += 1;
this.setState({ disabled: false });
}
|
13. Create a function named as add_New_View(). This function is used to add new view on current Scroll View items.
1
2
3
4
5
6
7
8
9
|
add_New_View = () => {
this.addNewElement = true;
const newlyAddedValue = { id: “id_” + this.index, text: this.index + 1 };
this.setState({
disabled: true,
valueArray: [...this.state.valueArray, newlyAddedValue]
});
}
|
14. Create a function named as remove_Selected_Item() with ID parameter. We would pass the ID of current selected ITEM in it so it will remove that item only.
1
2
3
4
5
6
7
8
9
10
11
12
13
|
remove_Selected_Item(id) {
this.addNewElement = false;
const newArray = [...this.state.valueArray];
newArray.splice(newArray.findIndex(ele => ele.id === id), 1);
this.setState(() => {
return {
valueArray: newArray
}
}, () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
});
}
|
15. Create the ScrollView component in render’s return block. The implement the Animated_Item class component to render items using animation.
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
|
render() {
return (
<View style={styles.container}>
<ScrollView
ref={scrollView => this.scrollView = scrollView}
onContentSizeChange={() => {
this.addNewElement && this.scrollView.scrollToEnd();
}}>
<View style={{ flex: 1, padding: 4 }}>
{this.state.valueArray.map(ele => {
return (
<Animated_Item
key={ele.id}
item={ele}
deleteItem={(id) => this.remove_Selected_Item(id)}
afterAnimationComplete={this.afterAnimationComplete}
/>
)
})}
</View>
</ScrollView>
<TouchableOpacity
activeOpacity={0.8}
style={styles.TouchableOpacityStyle}
disabled={this.state.disabled}
onPress={this.add_New_View}>
<Image
source={{ uri: ‘/wp-content/uploads/2017/11/Floating_Button.png’ }}
style={styles.FloatingButtonStyle}
/>
</TouchableOpacity>
</View>
);
}
|
16. Creating Style for both classes.
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
|
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: ‘#eee’,
justifyContent: ‘center’,
paddingTop: (Platform.OS == ‘ios’) ? 20 : 0
},
singleItemView: {
backgroundColor: ‘#FF6F00’,
alignItems: ‘flex-start’,
justifyContent: ‘center’,
paddingVertical: 16,
paddingLeft: 16,
margin: 5,
borderRadius: 8
},
singleItemText: {
color: ‘#fff’,
fontSize: 23,
paddingRight: 18
},
TouchableOpacityStyle: {
position: ‘absolute’,
width: 50,
height: 50,
alignItems: ‘center’,
justifyContent: ‘center’,
right: 30,
bottom: 30,
},
FloatingButtonStyle: {
resizeMode: ‘contain’,
width: 50,
height: 50,
},
deleteButton: {
position: ‘absolute’,
right: 10,
width: 25,
height: 25,
borderRadius: 18,
padding: 7,
justifyContent: ‘center’,
alignItems: ‘center’,
backgroundColor: ‘#fff’
},
removeIcon: {
width: ‘100%’,
fontSize: 20
}
});
|
17. 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
|
import React, { Component } from ‘react’;
import {
Animated,
Dimensions,
Image,
LayoutAnimation,
Platform,
ScrollView,
StyleSheet,
Text,
TouchableOpacity,
UIManager,
View
} from ‘react-native’;
const width = Dimensions.get(‘window’).width;
class Animated_Item extends Component {
constructor() {
super();
this.animatedValue = new Animated.Value(0);
if (Platform.OS === ‘android’) {
UIManager.setLayoutAnimationEnabledExperimental(true);
}
}
shouldComponentUpdate(nextProps, nextState) {
if (nextProps.item.id !== this.props.item.id) {
return true;
}
return false;
}
componentDidMount() {
Animated.timing(
this.animatedValue,
{
toValue: 0.5,
duration: 510,
useNativeDriver: true
}
).start(() => {
this.props.afterAnimationComplete();
});
}
deleteItem = () => {
Animated.timing(
this.animatedValue,
{
toValue: 1,
duration: 510,
useNativeDriver: true
}
).start(() => {
this.props.deleteItem(this.props.item.id);
});
}
render() {
const translate_Animation_Object = this.animatedValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [–width, 0, width]
});
const opacity_Animation_Object = this.animatedValue.interpolate({
inputRange: [0, 0.5, 1],
outputRange: [0, 1, 0]
});
return (
<Animated.View style={[
styles.singleItemView, {
transform: [{ translateX: translate_Animation_Object }],
opacity: opacity_Animation_Object
}]}>
<Text style={styles.singleItemText}>
Item {this.props.item.text}
</Text>
<TouchableOpacity
style={styles.deleteButton}
onPress={this.deleteItem}>
<Text style={styles.removeIcon}>{‘u00D7’}</Text>
</TouchableOpacity>
</Animated.View>
);
}
}
export default class App extends Component {
constructor() {
super();
this.state = { valueArray: [], disabled: false }
this.addNewElement = false;
this.index = 0;
}
afterAnimationComplete = () => {
this.index += 1;
this.setState({ disabled: false });
}
add_New_View = () => {
this.addNewElement = true;
const newlyAddedValue = { id: “id_” + this.index, text: this.index + 1 };
this.setState({
disabled: true,
valueArray: [...this.state.valueArray, newlyAddedValue]
});
}
remove_Selected_Item(id) {
this.addNewElement = false;
const newArray = [...this.state.valueArray];
newArray.splice(newArray.findIndex(ele => ele.id === id), 1);
this.setState(() => {
return {
valueArray: newArray
}
}, () => {
LayoutAnimation.configureNext(LayoutAnimation.Presets.easeInEaseOut);
});
}
render() {
return (
<View style={styles.container}>
<ScrollView
ref={scrollView => this.scrollView = scrollView}
onContentSizeChange={() => {
this.addNewElement && this.scrollView.scrollToEnd();
}}>
<View style={{ flex: 1, padding: 4 }}>
{this.state.valueArray.map(ele => {
return (
<Animated_Item
key={ele.id}
item={ele}
deleteItem={(id) => this.remove_Selected_Item(id)}
afterAnimationComplete={this.afterAnimationComplete}
/>
)
})}
</View>
</ScrollView>
<TouchableOpacity
activeOpacity={0.8}
style={styles.TouchableOpacityStyle}
disabled={this.state.disabled}
onPress={this.add_New_View}>
<Image
source={{ uri: ‘/wp-content/uploads/2017/11/Floating_Button.png’ }}
style={styles.FloatingButtonStyle}
/>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: ‘#eee’,
justifyContent: ‘center’,
paddingTop: (Platform.OS == ‘ios’) ? 20 : 0
},
singleItemView: {
backgroundColor: ‘#FF6F00’,
alignItems: ‘flex-start’,
justifyContent: ‘center’,
paddingVertical: 16,
paddingLeft: 16,
margin: 5,
borderRadius: 8
},
singleItemText: {
color: ‘#fff’,
fontSize: 23,
paddingRight: 18
},
TouchableOpacityStyle: {
position: ‘absolute’,
width: 50,
height: 50,
alignItems: ‘center’,
justifyContent: ‘center’,
right: 30,
bottom: 30,
},
FloatingButtonStyle: {
resizeMode: ‘contain’,
width: 50,
height: 50,
},
deleteButton: {
position: ‘absolute’,
right: 10,
width: 25,
height: 25,
borderRadius: 18,
padding: 7,
justifyContent: ‘center’,
alignItems: ‘center’,
backgroundColor: ‘#fff’
},
removeIcon: {
width: ‘100%’,
fontSize: 20
}
});
|
Screenshot: