@@ -3,11 +3,19 @@ import PropTypes from 'prop-types';
3
3
import React from 'react' ;
4
4
import { ScrollView , StyleSheet } from 'react-native' ;
5
5
import { Constants } from '../../helpers' ;
6
+ import { Colors } from '../../style' ;
6
7
import { BaseComponent } from '../../commons' ;
7
8
import View from '../view' ;
9
+ import Text from '../text' ;
10
+ import PageControl from '../pageControl' ;
8
11
import * as presenter from './CarouselPresenter' ;
9
12
10
13
14
+ const PAGE_CONTROL_POSITIONS = {
15
+ OVER : 'over' ,
16
+ UNDER : 'under'
17
+ }
18
+
11
19
/**
12
20
* @description : Carousel for scrolling pages horizontally
13
21
* @gif : https://media.giphy.com/media/l0HU7f8gjpRlMRhKw/giphy.gif, https://media.giphy.com/media/3oFzmcjX9OhpyckhcQ/giphy.gif
@@ -25,9 +33,13 @@ export default class Carousel extends BaseComponent {
25
33
*/
26
34
initialPage : PropTypes . number ,
27
35
/**
28
- * the page width (all pages should have the same width)
36
+ * the page width (all pages should have the same width). Does not work if passing 'loop' prop
29
37
*/
30
38
pageWidth : PropTypes . number ,
39
+ /**
40
+ * the spacing between the items
41
+ */
42
+ itemSpacings : PropTypes . number ,
31
43
/**
32
44
* if true, will have infinite scroll
33
45
*/
@@ -44,20 +56,39 @@ export default class Carousel extends BaseComponent {
44
56
* the carousel style
45
57
*/
46
58
containerStyle : PropTypes . oneOfType ( [ PropTypes . object , PropTypes . number , PropTypes . array ] ) ,
59
+ /**
60
+ * PageControl component props
61
+ */
62
+ pageControlProps : PropTypes . shape ( PageControl . propTypes ) ,
63
+ /**
64
+ * The position of the PageControl component ['over', 'under'], otherwise it won't display
65
+ */
66
+ pageControlPosition : PropTypes . oneOf ( Object . values ( PAGE_CONTROL_POSITIONS ) ) ,
67
+ /**
68
+ * whether to show a page counter (will not work with pageWidths)
69
+ */
70
+ showCounter : PropTypes . bool ,
71
+ /**
72
+ * the counter's text style
73
+ */
74
+ counterTextStyle : PropTypes . oneOfType ( [ PropTypes . object , PropTypes . number , PropTypes . array ] )
47
75
} ;
48
76
49
77
static defaultProps = {
50
- initialPage : 0
78
+ initialPage : 0 ,
79
+ itemSpacings : 12
51
80
} ;
52
81
82
+ static pageControlPositions = PAGE_CONTROL_POSITIONS ;
83
+
53
84
constructor ( props ) {
54
85
super ( props ) ;
55
86
56
87
this . carousel = React . createRef ( ) ;
57
- const defaultPageWidth = props . pageWidth || Constants . screenWidth ;
58
-
88
+ const defaultPageWidth = props . loop ? Constants . screenWidth : ( props . pageWidth + props . itemSpacings || Constants . screenWidth ) ;
89
+
59
90
this . state = {
60
- currentPage : props . initialPage ,
91
+ currentPage : this . shouldUsePageWidth ( ) ? this . getCalcIndex ( props . initialPage ) : props . initialPage ,
61
92
currentStandingPage : props . initialPage ,
62
93
pageWidth : defaultPageWidth ,
63
94
initialOffset : { x : presenter . calcOffset ( props , { currentPage : props . initialPage , pageWidth : defaultPageWidth } ) }
@@ -73,7 +104,7 @@ export default class Carousel extends BaseComponent {
73
104
}
74
105
75
106
onOrientationChanged = ( ) => {
76
- if ( ! this . props . pageWidth ) {
107
+ if ( ! this . props . pageWidth || this . props . loop ) {
77
108
this . setState ( { pageWidth : Constants . screenWidth } ) ;
78
109
this . goToPage ( this . state . currentPage , true ) ;
79
110
}
@@ -84,7 +115,8 @@ export default class Carousel extends BaseComponent {
84
115
}
85
116
86
117
updateOffset = ( animated = false ) => {
87
- const x = presenter . calcOffset ( this . props , this . state ) ;
118
+ const centerOffset = Constants . isIOS && this . shouldUsePageWidth ( ) ? ( Constants . screenWidth - this . state . pageWidth ) / 2 : 0 ;
119
+ const x = presenter . calcOffset ( this . props , this . state ) - centerOffset ;
88
120
89
121
if ( this . carousel ) {
90
122
this . carousel . current . scrollTo ( { x, animated} ) ;
@@ -111,6 +143,11 @@ export default class Carousel extends BaseComponent {
111
143
return index ;
112
144
}
113
145
146
+ shouldUsePageWidth ( ) {
147
+ const { loop, pageWidth} = this . props ;
148
+ return ! loop && pageWidth ;
149
+ }
150
+
114
151
onContentSizeChange = ( ) => {
115
152
// this is to handle initial scroll position (content offset)
116
153
if ( Constants . isAndroid ) {
@@ -151,8 +188,10 @@ export default class Carousel extends BaseComponent {
151
188
}
152
189
153
190
renderChild = ( child , key ) => {
191
+ const paddingLeft = this . shouldUsePageWidth ( ) ? this . props . itemSpacings : undefined ;
192
+
154
193
return (
155
- < View style = { { width : this . state . pageWidth } } key = { key } >
194
+ < View style = { { width : this . state . pageWidth , paddingLeft } } key = { key } >
156
195
{ child }
157
196
</ View >
158
197
) ;
@@ -165,7 +204,7 @@ export default class Carousel extends BaseComponent {
165
204
const childrenArray = React . Children . map ( children , ( child , index ) => {
166
205
return this . renderChild ( child , `${ index } ` ) ;
167
206
} ) ;
168
-
207
+
169
208
if ( loop ) {
170
209
childrenArray . unshift ( this . renderChild ( children [ length - 1 ] , `${ length - 1 } -clone` ) ) ;
171
210
childrenArray . push ( this . renderChild ( children [ 0 ] , `${ 0 } -clone` ) ) ;
@@ -174,32 +213,86 @@ export default class Carousel extends BaseComponent {
174
213
return childrenArray ;
175
214
}
176
215
216
+ renderPageControl ( ) {
217
+ const { pageControlPosition, pageControlProps} = this . props ;
218
+
219
+ if ( pageControlPosition ) {
220
+ const pagesCount = presenter . getChildrenLength ( this . props ) ;
221
+ const containerStyle = pageControlPosition === PAGE_CONTROL_POSITIONS . UNDER ?
222
+ { marginVertical : 16 } : { position : 'absolute' , bottom : 16 , alignSelf : 'center' } ;
223
+
224
+ return (
225
+ < PageControl
226
+ size = { 6 }
227
+ containerStyle = { containerStyle }
228
+ inactiveColor = { Colors . dark60 }
229
+ color = { Colors . dark20 }
230
+ { ...pageControlProps }
231
+ numOfPages = { pagesCount }
232
+ currentPage = { this . getCalcIndex ( this . state . currentPage ) }
233
+ />
234
+ ) ;
235
+ }
236
+ }
237
+
238
+ renderCounter ( ) {
239
+ const { pageWidth, showCounter, counterTextStyle} = this . props ;
240
+ const { currentPage} = this . state ;
241
+ const pagesCount = presenter . getChildrenLength ( this . props ) ;
242
+
243
+ if ( showCounter && ! pageWidth ) {
244
+ return (
245
+ < View center style = { this . styles . counter } >
246
+ < Text dark80 text90 style = { [ { fontWeight : 'bold' } , counterTextStyle ] } > { currentPage + 1 } /{ pagesCount } </ Text >
247
+ </ View >
248
+ ) ;
249
+ }
250
+ }
251
+
177
252
render ( ) {
178
- const { containerStyle, ...others } = this . props ;
179
- const { initialOffset} = this . state ;
253
+ const { containerStyle, itemSpacings , initialPage , ...others } = this . props ;
254
+ const { initialOffset, pageWidth } = this . state ;
180
255
256
+ const scrollContainerStyle = this . shouldUsePageWidth ( ) ? { paddingRight : itemSpacings } : undefined ;
257
+ const spacings = pageWidth === Constants . screenWidth ? 0 : itemSpacings ;
258
+ const initialBreak = pageWidth - ( Constants . screenWidth - pageWidth - spacings ) / 2 ;
259
+ const snapToOffsets = _ . times ( presenter . getChildrenLength ( this . props ) , ( index ) => initialBreak + index * pageWidth ) ;
260
+
181
261
return (
182
- < ScrollView
183
- { ...others }
184
- ref = { this . carousel }
185
- style = { [ containerStyle , { flexGrow : 1 } ] }
186
- horizontal
187
- showsHorizontalScrollIndicator = { false }
188
- pagingEnabled
189
- onScroll = { this . onScroll }
190
- scrollEventThrottle = { 200 }
191
- contentOffset = { initialOffset }
192
- onContentSizeChange = { this . onContentSizeChange }
193
- onMomentumScrollEnd = { this . onMomentumScrollEnd }
194
- >
195
- { this . renderChildren ( ) }
196
- </ ScrollView >
262
+ < View style = { containerStyle } >
263
+ < ScrollView
264
+ { ...others }
265
+ ref = { this . carousel }
266
+ contentContainerStyle = { scrollContainerStyle }
267
+ horizontal
268
+ showsHorizontalScrollIndicator = { false }
269
+ snapToOffsets = { snapToOffsets }
270
+ decelerationRate = "fast"
271
+ contentOffset = { initialOffset } // iOS only
272
+ scrollEventThrottle = { 200 }
273
+ onContentSizeChange = { this . onContentSizeChange }
274
+ onScroll = { this . onScroll }
275
+ onMomentumScrollEnd = { this . onMomentumScrollEnd }
276
+ >
277
+ { this . renderChildren ( ) }
278
+ </ ScrollView >
279
+ { this . renderPageControl ( ) }
280
+ { this . renderCounter ( ) }
281
+ </ View >
197
282
) ;
198
283
}
199
284
}
200
285
201
286
function createStyles ( ) {
202
287
return StyleSheet . create ( {
203
-
288
+ counter : {
289
+ paddingHorizontal : 8 ,
290
+ paddingVertical : 3 , // height: 24,
291
+ borderRadius : 20 ,
292
+ backgroundColor : Colors . rgba ( Colors . black , 0.6 ) ,
293
+ position : 'absolute' ,
294
+ top : 12 ,
295
+ right : 12
296
+ }
204
297
} ) ;
205
298
}
0 commit comments