Skip to content

Commit 9c7f5be

Browse files
author
观通
committedAug 30, 2016
add rn-carousel.
1 parent 86c147d commit 9c7f5be

File tree

12 files changed

+479
-10
lines changed

12 files changed

+479
-10
lines changed
 

‎components/carousel/ViewPager.tsx

+326
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,326 @@
1+
import * as React from 'react';
2+
import reactMixin from 'react-mixin';
3+
import ReactTimerMixin from 'react-timer-mixin';
4+
import Pagination from '../pagination';
5+
import {
6+
View,
7+
ScrollView,
8+
ViewPagerAndroid,
9+
Platform,
10+
Dimensions,
11+
ViewStyle,
12+
ActivityIndicator,
13+
} from 'react-native';
14+
import styles from './style';
15+
16+
let { width, height } = Dimensions.get('window');
17+
18+
export interface ViewPagerProps {
19+
selectedIndex: number;
20+
bounces?: boolean;
21+
children?: any;
22+
style?: any;
23+
dots?: boolean;
24+
autoplay?: boolean;
25+
autoplayTimeout?: number;
26+
infinite?: boolean;
27+
onScrollBeginDrag: Function;
28+
onMomentumScrollEnd: Function;
29+
loadMinimal?: boolean;
30+
loadMinimalSize?: number;
31+
}
32+
33+
export default class ViewPager extends React.Component<ViewPagerProps, any> {
34+
static defaultProps = {
35+
bounces: true,
36+
infinite: true,
37+
dots: true,
38+
loadMinimal: false,
39+
loadMinimalSize: 1,
40+
autoplay: false,
41+
autoplayTimeout: 2.5,
42+
selectedIndex: 0,
43+
};
44+
autoplayTimer: any;
45+
setTimeout: any;
46+
47+
constructor(props) {
48+
super(props);
49+
this.state = this.initState(this.props);
50+
51+
this.onScrollEnd = this.onScrollEnd.bind(this);
52+
this.onScrollBegin = this.onScrollBegin.bind(this);
53+
this.onScrollEndDrag = this.onScrollEndDrag.bind(this);
54+
}
55+
56+
componentDidMount() {
57+
this.autoplay();
58+
}
59+
60+
initState(props) {
61+
// set the current state
62+
// const state = this.state || {};
63+
const count = props.children ? props.children.length || 1 : 0;
64+
width = props.width || width;
65+
height = props.height || height;
66+
const selectedIndex = count > 1 ? Math.min(props.selectedIndex, count - 1) : 0;
67+
68+
let initState = {
69+
width,
70+
height,
71+
isScrolling: false,
72+
autoplayEnd: false,
73+
loopJump: false,
74+
count,
75+
selectedIndex,
76+
offset: {
77+
x: width * (selectedIndex + (props.infinite ? 1 : 0)),
78+
y: 0,
79+
},
80+
};
81+
82+
return initState;
83+
}
84+
85+
loopJump() {
86+
if (this.state.loopJump) {
87+
const index = this.state.selectedIndex + (this.props.infinite ? 1 : 0);
88+
setTimeout(() => (this.refs as any).scrollview.setPageWithoutAnimation
89+
&& (this.refs as any).scrollview.setPageWithoutAnimation(index), 50);
90+
}
91+
}
92+
93+
autoplay() {
94+
if (
95+
!Array.isArray(this.props.children)
96+
|| !this.props.autoplay
97+
|| this.state.isScrolling
98+
|| this.state.autoplayEnd
99+
) {
100+
return;
101+
}
102+
103+
clearTimeout(this.autoplayTimer);
104+
105+
this.autoplayTimer = this.setTimeout(() => {
106+
if (!this.props.infinite && ( this.state.selectedIndex === this.state.count - 1)) {
107+
return this.setState({ autoplayEnd: true });
108+
}
109+
this.scrollNextPage();
110+
}, this.props.autoplayTimeout * 1000);
111+
}
112+
113+
onScrollBegin(e) {
114+
this.setState({ isScrolling: true });
115+
116+
this.setTimeout(() => {
117+
if (this.props.onScrollBeginDrag) {
118+
this.props.onScrollBeginDrag(e, this.state, this);
119+
}
120+
});
121+
}
122+
123+
onScrollEnd(e) {
124+
this.setState({isScrolling: false});
125+
126+
// android incompatible
127+
if (!e.nativeEvent.contentOffset) {
128+
e.nativeEvent.contentOffset = {x: e.nativeEvent.position * this.state.width};
129+
}
130+
131+
this.updateIndex(e.nativeEvent.contentOffset);
132+
133+
this.setTimeout(() => {
134+
this.autoplay();
135+
this.loopJump();
136+
if (this.props.onMomentumScrollEnd) {
137+
this.props.onMomentumScrollEnd(e, this.state, this);
138+
}
139+
});
140+
}
141+
142+
onScrollEndDrag(e) {
143+
const { offset, selectedIndex, count } = this.state;
144+
const previousOffset = offset.x;
145+
const newOffset = e.nativeEvent.x;
146+
147+
if (previousOffset === newOffset && (selectedIndex === 0 || selectedIndex === count - 1)) {
148+
this.setState({
149+
isScrolling: false,
150+
});
151+
}
152+
}
153+
154+
updateIndex(offset) {
155+
let state = this.state;
156+
let selectedIndex = state.selectedIndex;
157+
let diff = offset.x - state.offset.x;
158+
let step = state.width;
159+
let loopJump = false;
160+
161+
// Do nothing if offset no change.
162+
if (!diff) {
163+
return;
164+
}
165+
166+
selectedIndex = parseInt(selectedIndex + Math.round(diff / step), 10);
167+
168+
if (this.props.infinite) {
169+
if (selectedIndex <= -1) {
170+
selectedIndex = state.count - 1;
171+
offset.x = step * state.count;
172+
loopJump = true;
173+
} else if (selectedIndex >= state.count) {
174+
selectedIndex = 0;
175+
offset.x = step;
176+
loopJump = true;
177+
}
178+
}
179+
180+
this.setState({
181+
selectedIndex,
182+
offset,
183+
loopJump: loopJump,
184+
});
185+
}
186+
187+
scrollNextPage() {
188+
if (this.state.isScrolling || this.state.count < 2) {
189+
return;
190+
}
191+
let state = this.state;
192+
let diff = (this.props.infinite ? 1 : 0) + this.state.selectedIndex + 1;
193+
let x = 0;
194+
let y = 0;
195+
x = diff * state.width;
196+
197+
if (Platform.OS === 'android') {
198+
(this.refs as any).scrollview.setPage(diff);
199+
} else {
200+
(this.refs as any).scrollview.scrollTo({ x, y });
201+
}
202+
203+
this.setState({
204+
isScrolling: true,
205+
autoplayEnd: false,
206+
});
207+
208+
// trigger onScrollEnd manually in android
209+
if (Platform.OS === 'android') {
210+
this.setTimeout(() => {
211+
this.onScrollEnd({
212+
nativeEvent: {
213+
position: diff,
214+
},
215+
});
216+
}, 0);
217+
}
218+
}
219+
220+
renderContent(pages) {
221+
if (Platform.OS === 'ios') {
222+
return (
223+
<ScrollView ref="scrollview"
224+
{...this.props}
225+
horizontal={true}
226+
pagingEnabled={true}
227+
bounces={!!this.props.bounces}
228+
scrollEventThrottle={100}
229+
removeClippedSubviews={true}
230+
automaticallyAdjustContentInsets={false}
231+
directionalLockEnabled={true}
232+
showsHorizontalScrollIndicator={false}
233+
showsVerticalScrollIndicator={false}
234+
contentContainerStyle={[styles.wrapper, this.props.style]}
235+
contentOffset={this.state.offset}
236+
onScrollBeginDrag={this.onScrollBegin}
237+
onMomentumScrollEnd={this.onScrollEnd}
238+
onScrollEndDrag={this.onScrollEndDrag}>
239+
{pages}
240+
</ScrollView>
241+
);
242+
} else {
243+
return (
244+
<ViewPagerAndroid
245+
{...this.props}
246+
ref="scrollview"
247+
initialPage={this.props.infinite ? this.state.selectedIndex + 1 : this.state.selectedIndex}
248+
onPageSelected={this.onScrollEnd}
249+
style={{flex: 1}}
250+
>
251+
{pages}
252+
</ViewPagerAndroid>
253+
);
254+
}
255+
}
256+
257+
renderDots(index) {
258+
return (
259+
<Pagination
260+
style={styles.pagination}
261+
current={index}
262+
mode="pointer"
263+
total={5}
264+
size="small"
265+
/>
266+
);
267+
}
268+
269+
render() {
270+
let state = this.state;
271+
let props = this.props;
272+
let children = props.children;
273+
let selectedIndex = state.selectedIndex;
274+
let count = state.count;
275+
let infinite = props.infinite;
276+
// let key = 0;
277+
let loopVal = infinite ? 1 : 0;
278+
279+
let pages: any = [];
280+
281+
let pageStyle = [{width: state.width, height: state.height}, styles.slide];
282+
let pageStyleLoading = {
283+
width: this.state.width,
284+
height: this.state.height,
285+
justifyContent: 'center',
286+
alignItems: 'center',
287+
} as ViewStyle;
288+
289+
// For make infinite at least count > 1
290+
if (count > 1) {
291+
pages = Object.keys(children);
292+
if (infinite) {
293+
pages.unshift(count - 1 + '');
294+
pages.push('0');
295+
}
296+
297+
pages = pages.map((page, i) => {
298+
if (props.loadMinimal) {
299+
if (i >= (selectedIndex + loopVal - props.loadMinimalSize)
300+
&& i <= (selectedIndex + loopVal + props.loadMinimalSize)
301+
) {
302+
return (<View style={pageStyle} key={i}>{children[page]}</View>);
303+
} else {
304+
return (<View style={pageStyleLoading} key={`loading-${i}`}><ActivityIndicator /></View>);
305+
}
306+
} else {
307+
return (<View style={pageStyle} key={i}>{children[page]}</View>);
308+
}
309+
});
310+
} else {
311+
pages = (<View style={pageStyle}>{children}</View>);
312+
}
313+
314+
return (
315+
<View style={[styles.container, {
316+
width: state.width,
317+
height: state.height,
318+
}]}>
319+
{this.renderContent(pages)}
320+
{props.dots && this.renderDots(this.state.selectedIndex)}
321+
</View>
322+
);
323+
}
324+
}
325+
326+
reactMixin(ViewPager.prototype, ReactTimerMixin);

‎components/carousel/demo/basic.tsx

+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import * as React from 'react';
2+
import { StyleSheet, Image, View, ViewStyle } from 'react-native';
3+
import { Carousel } from 'antd-mobile';
4+
5+
export default class BasicCarouselExample extends React.Component<any, any> {
6+
render() {
7+
return (
8+
<View>
9+
<Carousel
10+
style={styles.wrapper}
11+
autoplayTimeout={2}
12+
selectedIndex={1}
13+
autoplay={true}
14+
infinite={true}
15+
height={240}
16+
loop={true}
17+
>
18+
<View style={styles.slide}>
19+
<Image style={styles.image} source={{uri: 'https://zos.alipayobjects.com/rmsportal/JKLbnnQSjYXnfUq.jpg'}} />
20+
</View>
21+
<View style={styles.slide}>
22+
<Image style={styles.image} source={{uri: 'https://zos.alipayobjects.com/rmsportal/IHnTTMjYUgthhoW.jpg'}} />
23+
</View>
24+
<View style={styles.slide}>
25+
<Image style={styles.image} source={{uri: 'https://zos.alipayobjects.com/rmsportal/ccvTCqmTFessEyC.jpg'}} />
26+
</View>
27+
<View style={styles.slide}>
28+
<Image style={styles.image} source={{uri: 'https://zos.alipayobjects.com/rmsportal/SndhMCpKtliuiDR.jpg'}} />
29+
</View>
30+
<View style={styles.slide}>
31+
<Image style={styles.image} source={{uri: 'https://zos.alipayobjects.com/rmsportal/IDTtiHCFYvnGJjl.jpg'}} />
32+
</View>
33+
</Carousel>
34+
</View>
35+
);
36+
}
37+
}
38+
39+
const styles = StyleSheet.create({
40+
wrapper: {
41+
height: 200,
42+
},
43+
slide: {
44+
flex: 1,
45+
justifyContent: 'center',
46+
backgroundColor: 'transparent',
47+
} as ViewStyle,
48+
slide1: {
49+
flex: 1,
50+
justifyContent: 'center',
51+
alignItems: 'center',
52+
backgroundColor: '#9DD6EB',
53+
} as ViewStyle,
54+
slide2: {
55+
flex: 1,
56+
justifyContent: 'center',
57+
alignItems: 'center',
58+
backgroundColor: '#97CAE5',
59+
} as ViewStyle,
60+
slide3: {
61+
flex: 1,
62+
justifyContent: 'center',
63+
alignItems: 'center',
64+
backgroundColor: '#92BBD9',
65+
} as ViewStyle,
66+
text: {
67+
color: '#fff',
68+
fontSize: 30,
69+
fontWeight: 'bold',
70+
} as ViewStyle,
71+
image: {
72+
flex: 1,
73+
},
74+
});

‎components/carousel/index.md

+6-6
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@ english: Carousel
2020

2121
| 参数 | 说明 | 类型 | 默认值 |
2222
|------------------|----------------------------------------------|----------|---------------------------------|
23-
| mode | 展示模式,可取banner,card,提供定制化的顶部banner型和卡片型两种样式 | String ||
24-
| effect | 动画效果函数,可取 scrollx, fade | String | scrollx |
23+
| mode (`web only`) | 展示模式,可取banner,card,提供定制化的顶部banner型和卡片型两种样式 | String ||
24+
| effect (`web only`) | 动画效果函数,可取 scrollx, fade | String | scrollx |
2525
| dots | 是否显示面板指示点 | Boolean | true |
26-
| vertical | 垂直显示 | Boolean | false |
26+
| vertical (`web only`) | 垂直显示 | Boolean | false |
2727
| autoplay | 是否自动切换 | Boolean | false |
28-
| easing | 动画效果 | String | linear |
29-
| beforeChange | 切换面板的回调 | function(from, to) | 无
30-
| afterChange | 切换面板的回调 | function(current) | 无
28+
| easing (`web only`) | 动画效果 | String | linear |
29+
| beforeChange (`web only`) | 切换面板的回调 | function(from, to) | 无
30+
| afterChange (`web only`) | 切换面板的回调 | function(current) | 无
3131

3232
更多参数可参考:https://github.com/akiran/react-slick

‎components/carousel/index.tsx

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import * as React from 'react';
2+
import { Text } from 'react-native';
3+
import ViewPager from './ViewPager';
4+
5+
export default React.createClass({
6+
render() {
7+
const { children } = this.props;
8+
9+
if (!children) {
10+
return (
11+
<Text style={{backgroundColor: 'white'}}>
12+
You are supposed to add children inside Carousel
13+
</Text>
14+
);
15+
}
16+
17+
return (
18+
<ViewPager style={[this.props.style]} {...this.props} bounces={true}>
19+
{children}
20+
</ViewPager>
21+
);
22+
},
23+
});

‎components/carousel/style/index.tsx

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { StyleSheet } from 'react-native';
2+
3+
export default StyleSheet.create({
4+
wrapper: {
5+
backgroundColor: 'transparent',
6+
},
7+
container: {
8+
flex: 1,
9+
backgroundColor: 'transparent',
10+
position: 'relative',
11+
},
12+
slide: {
13+
backgroundColor: 'transparent',
14+
},
15+
scrollview: {
16+
flex: 1,
17+
backgroundColor: 'transparent',
18+
},
19+
pagination: {
20+
flex: 1,
21+
position: 'absolute',
22+
bottom: 10,
23+
left: 0,
24+
right: 0,
25+
alignItems: 'center',
26+
},
27+
});

‎components/pagination/demo/basic.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -49,15 +49,15 @@ export default class BasicPaginationExample extends React.Component<any, any> {
4949
<WhiteSpace size="lg" />
5050
<WingBlank size="lg">
5151
<Pagination
52-
mode="point"
52+
mode="pointer"
5353
total={5}
5454
size="large"
5555
/>
5656
</WingBlank>
5757
<WhiteSpace size="lg" />
5858
<WingBlank size="lg">
5959
<Pagination
60-
mode="point"
60+
mode="pointer"
6161
total={5}
6262
size="small"
6363
/>

‎components/pagination/index.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ english: Pagination
1818
### Pagination
1919
| 参数 | 说明 | 类型 | 可选值 |默认值 |
2020
|-----------|------------------------------------------|------------|-------|--------|
21-
| mode | 形态 | string | `button` or `number` or `point` | `button` |
21+
| mode | 形态 | string | `button` or `number` or `pointer` | `button` |
2222
| current | 当前索引 | number | ||
2323
| total | 数据总数 | number | | 0 |
2424
| simple | 是否显示数值 | boolean | `true` or `false` | true |

‎components/pagination/index.tsx

+1-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export default class Pagination extends React.Component<PaginationProps, any> {
112112
</View>
113113
);
114114
break;
115-
case 'point':
115+
case 'pointer':
116116
const indexes = this.getIndexes(total);
117117
const spaceStyle = size === 'large'
118118
? styles.spaceLargeStyle : styles.spaceSmallStyle;

‎index.js

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export { default as Badge } from './components/badge/';
44
export { default as Button } from './components/button/';
55
export { default as Checkbox } from './components/checkbox/';
66
export { default as Card } from './components/card/';
7+
export { default as Carousel } from './components/carousel/';
78
export { default as DatePicker } from './components/date-picker/';
89
export { default as Drawer } from './components/drawer/';
910
export { default as Flex } from './components/flex/';

‎rn-kitchen-sink/demoList.js

+6
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@ module.exports = {
4444
icon: 'https://os.alipayobjects.com/rmsportal/kkQBRgZgcqSyMPS.png',
4545
module: require('../components/card/demo/basic'),
4646
},
47+
{
48+
title: 'Carousel', // 必须
49+
description: '跑马灯',
50+
icon: 'https://os.alipayobjects.com/rmsportal/ifHkrPIiJFcMNzT.png',
51+
module: require('../components/carousel/demo/basic'), // 必须
52+
},
4753
{
4854
title: 'Checkbox', // 必须
4955
description: '复选框',

‎typings/custom.d.ts

+6
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ declare module 'antd-mobile' {
55
Badge: any;
66
Button: any;
77
Card: any;
8+
Carousel: any;
89
Checkbox: any;
910
DatePicker: any;
1011
Drawer: any;
@@ -168,6 +169,11 @@ declare module 'react-native-camera-roll-picker' {
168169
export default exports
169170
}
170171

172+
declare module 'react-mixin'{
173+
var exports: any
174+
export default exports
175+
}
176+
171177
declare module 'react-timer-mixin'{
172178
var exports: any
173179
export default exports

‎typings/globals/react-native/index.d.ts

+6
Original file line numberDiff line numberDiff line change
@@ -4100,6 +4100,12 @@ declare namespace __React {
41004100
*/
41014101
onScroll?: (event?: { nativeEvent: NativeScrollEvent }) => void
41024102

4103+
onScrollBeginDrag?: (event?: { nativeEvent: NativeScrollEvent }) => void
4104+
4105+
onScrollEndDrag?: (event?: { nativeEvent: NativeScrollEvent }) => void
4106+
4107+
onMomentumScrollEnd?: (event?: { nativeEvent: NativeScrollEvent }) => void
4108+
41034109
/**
41044110
* When true the scroll view stops on multiples of the scroll view's size
41054111
* when scrolling. This can be used for horizontal pagination. The default

0 commit comments

Comments
 (0)
Please sign in to comment.