@@ -12,25 +12,49 @@ import {
12
12
} from "react-native" ;
13
13
14
14
const MODAL_ANIM_DURATION = 300 ;
15
- const MODAL_BACKDROP_OPACITY = 0.4 ;
16
-
17
- const IOS_CONTENT_ANIMATION = {
18
- from : { opacity : 0 , scale : 1.2 } ,
19
- 0.5 : { opacity : 1 , scale : 1.1 } ,
20
- to : { opacity : 1 , scale : 1 } ,
21
- } ;
22
-
23
- const ANDROID_CONTENT_ANIMATION = {
24
- from : { opacity : 0 , scale : 0.3 } ,
25
- 0.5 : { opacity : 1 , scale : 0.7 } ,
26
- to : { opacity : 1 , scale : 1 } ,
27
- } ;
28
-
29
- const OTHER_OS_CONTENT_ANIMATION = {
30
- from : { opacity : 0 , scale : 0.3 } ,
31
- 0.5 : { opacity : 1 , scale : 0.7 } ,
32
- to : { opacity : 1 , scale : 1 } ,
33
- } ;
15
+ const MODAL_BACKDROP_OPACITY = 0.3 ;
16
+
17
+ const CONTENT_ANIMATION_IN = Platform . select ( {
18
+ ios : {
19
+ opacity : {
20
+ inputRange : [ 0 , 1 ] ,
21
+ outputRange : [ 0 , 1 ] ,
22
+ } ,
23
+ scale : {
24
+ inputRange : [ 0 , 0.5 , 1 ] ,
25
+ outputRange : [ 1.2 , 1.1 , 1 ] ,
26
+ } ,
27
+ } ,
28
+ android : {
29
+ opacity : {
30
+ inputRange : [ 0 , 0.5 , 1 ] ,
31
+ outputRange : [ 0 , 1 , 1 ] ,
32
+ } ,
33
+ scale : {
34
+ inputRange : [ 0 , 1 ] ,
35
+ outputRange : [ 0.3 , 1 ] ,
36
+ } ,
37
+ } ,
38
+ default : {
39
+ opacity : {
40
+ inputRange : [ 0 , 0.5 , 1 ] ,
41
+ outputRange : [ 0 , 1 , 1 ] ,
42
+ } ,
43
+ scale : {
44
+ inputRange : [ 0 , 1 ] ,
45
+ outputRange : [ 0.3 , 1 ] ,
46
+ } ,
47
+ } ,
48
+ } ) ;
49
+
50
+ const CONTENT_ANIMATION_OUT = Platform . select ( {
51
+ default : {
52
+ opacity : {
53
+ inputRange : [ 0 , 1 ] ,
54
+ outputRange : [ 0 , 1 ] ,
55
+ } ,
56
+ } ,
57
+ } ) ;
34
58
35
59
export class Modal extends Component {
36
60
static propTypes = {
@@ -48,6 +72,7 @@ export class Modal extends Component {
48
72
49
73
state = {
50
74
visible : this . props . visible ,
75
+ currentAnimation : "none" ,
51
76
deviceWidth : Dimensions . get ( "window" ) . width ,
52
77
deviceHeight : Dimensions . get ( "window" ) . height ,
53
78
} ;
@@ -94,27 +119,33 @@ export class Modal extends Component {
94
119
} ;
95
120
96
121
show = ( ) => {
97
- this . setState ( { visible : true } ) ;
98
- Animated . timing ( this . animVal , {
99
- easing : Easing . inOut ( Easing . quad ) ,
100
- // Using native driver in the modal makes the content flash
101
- useNativeDriver : false ,
102
- duration : MODAL_ANIM_DURATION ,
103
- toValue : 1 ,
104
- } ) . start ( ) ;
122
+ this . setState ( { visible : true , currentAnimation : "in" } , ( ) => {
123
+ Animated . timing ( this . animVal , {
124
+ easing : Easing . inOut ( Easing . quad ) ,
125
+ // Using native driver in the modal makes the content flash
126
+ useNativeDriver : false ,
127
+ duration : MODAL_ANIM_DURATION ,
128
+ toValue : 1 ,
129
+ } ) . start ( ( ) => {
130
+ this . setState ( { currentAnimation : "none" } ) ;
131
+ } ) ;
132
+ } ) ;
105
133
} ;
106
134
107
135
hide = ( ) => {
108
- Animated . timing ( this . animVal , {
109
- easing : Easing . inOut ( Easing . quad ) ,
110
- // Using native driver in the modal makes the content flash
111
- useNativeDriver : false ,
112
- duration : MODAL_ANIM_DURATION ,
113
- toValue : 0 ,
114
- } ) . start ( ( ) => {
115
- if ( this . _isMounted ) {
116
- this . setState ( { visible : false } , this . props . onHide ) ;
117
- }
136
+ this . setState ( { animationDirection : "out" } , ( ) => {
137
+ Animated . timing ( this . animVal , {
138
+ easing : Easing . inOut ( Easing . quad ) ,
139
+ // Using native driver in the modal makes the content flash
140
+ useNativeDriver : false ,
141
+ duration : MODAL_ANIM_DURATION ,
142
+ toValue : 0 ,
143
+ } ) . start ( ( ) => {
144
+ if ( this . _isMounted ) {
145
+ this . setState ( { currentAnimation : "none" } ) ;
146
+ this . setState ( { visible : false } , this . props . onHide ) ;
147
+ }
148
+ } ) ;
118
149
} ) ;
119
150
} ;
120
151
@@ -125,7 +156,7 @@ export class Modal extends Component {
125
156
contentStyle,
126
157
...otherProps
127
158
} = this . props ;
128
- const { deviceHeight, deviceWidth, visible } = this . state ;
159
+ const { currentAnimation , deviceHeight, deviceWidth, visible } = this . state ;
129
160
130
161
const backdropAnimatedStyle = {
131
162
opacity : this . animVal . interpolate ( {
@@ -134,46 +165,38 @@ export class Modal extends Component {
134
165
} ) ,
135
166
} ;
136
167
137
- const contentAnimationSteps = Platform . select ( {
138
- ios : [
139
- IOS_CONTENT_ANIMATION . from ,
140
- IOS_CONTENT_ANIMATION [ "0.5" ] ,
141
- IOS_CONTENT_ANIMATION . to ,
142
- ] ,
143
- android : [
144
- ANDROID_CONTENT_ANIMATION . from ,
145
- ANDROID_CONTENT_ANIMATION [ "0.5" ] ,
146
- ANDROID_CONTENT_ANIMATION . to ,
147
- ] ,
148
- default : [
149
- OTHER_OS_CONTENT_ANIMATION . from ,
150
- OTHER_OS_CONTENT_ANIMATION [ "0.5" ] ,
151
- OTHER_OS_CONTENT_ANIMATION . to ,
152
- ] ,
153
- } ) ;
168
+ const contentAnimatedStyle =
169
+ currentAnimation === "in"
170
+ ? {
171
+ opacity : this . animVal . interpolate ( {
172
+ inputRange : CONTENT_ANIMATION_IN . opacity . inputRange ,
173
+ outputRange : CONTENT_ANIMATION_IN . opacity . outputRange ,
174
+ extrapolate : "clamp" ,
175
+ } ) ,
176
+ transform : [
177
+ {
178
+ scale : this . animVal . interpolate ( {
179
+ inputRange : CONTENT_ANIMATION_IN . scale . inputRange ,
180
+ outputRange : CONTENT_ANIMATION_IN . scale . outputRange ,
181
+ extrapolate : "clamp" ,
182
+ } ) ,
183
+ } ,
184
+ ] ,
185
+ }
186
+ : {
187
+ opacity : this . animVal . interpolate ( {
188
+ inputRange : CONTENT_ANIMATION_OUT . opacity . inputRange ,
189
+ outputRange : CONTENT_ANIMATION_OUT . opacity . outputRange ,
190
+ extrapolate : "clamp" ,
191
+ } ) ,
192
+ } ;
154
193
155
- const contentAnimatedStyle = {
156
- opacity : this . animVal . interpolate ( {
157
- inputRange : [ 0 , 0.5 , 1 ] ,
158
- outputRange : contentAnimationSteps . map ( ( x ) => x . opacity ) ,
159
- extrapolate : "clamp" ,
160
- } ) ,
161
- transform : [
162
- {
163
- scale : this . animVal . interpolate ( {
164
- inputRange : [ 0 , 0.5 , 1 ] ,
165
- outputRange : contentAnimationSteps . map ( ( x ) => x . scale ) ,
166
- extrapolate : "clamp" ,
167
- } ) ,
168
- } ,
169
- ] ,
170
- } ;
171
194
return (
172
195
< ReactNativeModal
173
196
transparent
174
197
animationType = "none"
175
- visible = { visible }
176
198
{ ...otherProps }
199
+ visible = { visible }
177
200
>
178
201
< TouchableWithoutFeedback onPress = { onBackdropPress } >
179
202
< Animated . View
@@ -186,8 +209,15 @@ export class Modal extends Component {
186
209
</ TouchableWithoutFeedback >
187
210
{ visible && (
188
211
< Animated . View
189
- style = { [ styles . content , contentAnimatedStyle , contentStyle ] }
212
+ style = { [ styles . content , contentAnimatedStyle ] }
190
213
pointerEvents = "box-none"
214
+ // Setting "needsOffscreenAlphaCompositing" solves a janky elevation
215
+ // animation on android. We should set it only while animation
216
+ // to avoid using more memory than needed.
217
+ // See: https://github.com/facebook/react-native/issues/23090
218
+ needsOffscreenAlphaCompositing = { [ "in" , "out" ] . includes (
219
+ currentAnimation
220
+ ) }
191
221
>
192
222
{ children }
193
223
</ Animated . View >
@@ -198,14 +228,6 @@ export class Modal extends Component {
198
228
}
199
229
200
230
const styles = StyleSheet . create ( {
201
- container : {
202
- flex : 1 ,
203
- position : "absolute" ,
204
- top : 0 ,
205
- left : 0 ,
206
- right : 0 ,
207
- bottom : 0 ,
208
- } ,
209
231
backdrop : {
210
232
position : "absolute" ,
211
233
top : 0 ,
0 commit comments