react-native-komect-uikit
Version:
React Native UI Toolkit
275 lines (233 loc) • 8.19 kB
JavaScript
/**
* @Author: will
* @Date: 2017-06-19T17:49:44+08:00
* @Filename: Carousel.js
* @Last modified by: will
* @Last modified time: 2017-06-20T15:05:55+08:00
*/
// Carousel.js
'use strict';
import React, {Component, PropTypes} from 'react';
import {StyleSheet, View, ScrollView} from 'react-native';
import Theme from '../themes/Theme';
import CarouselControl from './CarouselControl';
export default class Carousel extends Component {
static propTypes = {
...ScrollView.propTypes,
carousel: PropTypes.bool, //是否开启轮播
interval: PropTypes.number, //每页停留时间
direction: PropTypes.oneOf(['forward', 'backward']), //轮播方向
startIndex: PropTypes.number, //起始页面编号,从0开始
cycle: PropTypes.bool, //是否循环
control: PropTypes.oneOfType([PropTypes.bool, PropTypes.element]),
onChange: PropTypes.func, //(index, total) 页面改变时调用
};
static defaultProps = {
...ScrollView.defaultProps,
horizontal: true, //修改为false是纵向滚动
pagingEnabled: true,
showsHorizontalScrollIndicator: false,
showsVerticalScrollIndicator: false,
alwaysBounceHorizontal: false,
alwaysBounceVertical: false,
bounces: false,
automaticallyAdjustContentInsets: false,
scrollEventThrottle: 200,
scrollsToTop: false,
carousel: true,
interval: 3000,
direction: 'forward',
startIndex: 0,
cycle: true,
control: false,
};
static Control = CarouselControl;
constructor(props) {
super(props);
this.state = {
width: 0,
height: 0,
pageIndex: 0,
};
this.cardIndex = null;
this.initByProps(props);
this.setupTimer();
}
componentDidMount() {
super.componentDidMount && super.componentDidMount();
setTimeout(() => this.scrollToCard(this.cardIndex, false), 50);
}
componentWillUnmount() {
super.componentWillUnmount && super.componentWillUnmount();
this.removeTimer();
}
componentWillReceiveProps(nextProps) {
this.initByProps(nextProps);
this.setupTimer();
}
//滚动到指定页
scrollToPage(index, animated = true) {
this.scrollToCard(this.cycle ? index + 1 : index, animated);
}
//滚动到下一页
scrollToNextPage(animated = true) {
this.scrollToNextCard(animated);
}
//初始化轮播参数
initByProps(props) {
let {children, carousel, direction, startIndex, cycle} = props;
//页数
this.pageCount = children ? (children instanceof Array ? children.length : 1) : 0;
let multiPage = this.pageCount > 1;
//是否轮播
this.carousel = carousel && multiPage;
//是否循环
this.cycle = cycle && multiPage;
//是否正向轮播(从左往右顺序轮播,卡片从右往左滚动)
this.forward = direction === 'forward';
//卡片数量,card定义:轮播中的页面序列,如为循环播放则首尾各多一页,如页面为0-1-2,则cards为2-0-1-2-0
this.cardCount = multiPage && this.cycle ? this.pageCount + 2 : this.pageCount;
if (this.cardIndex === null || this.cardIndex >= this.cardCount)
this.cardIndex = multiPage && this.cycle ? startIndex + 1 : startIndex;
//下一页卡片步进
this.step = this.forward ? 1 : -1;
}
//设置定时器,开启轮播时在interval毫秒之后滚动到下一卡片
setupTimer() {
this.removeTimer();
if (!this.carousel) return;
this.timer = setTimeout(() => {
this.timer = null;
this.scrollToNextCard();
}, this.props.interval);
}
//删除定时器
removeTimer() {
if (this.timer) {
clearTimeout(this.timer);
this.timer = null;
}
}
//滚动到指定卡片
scrollToCard(cardIndex, animated = true) {
let {width, height} = this.state;
if (cardIndex < 0) cardIndex = 0;
else if (cardIndex >= this.cardCount) cardIndex = this.cardCount - 1;
if (this.props.horizontal)
this.refs.scrollView.scrollTo({x: width * cardIndex, y: 0, animated: animated});
else this.refs.scrollView.scrollTo({x: 0, y: height * cardIndex, animated: animated});
}
//滚动到下一张卡片
scrollToNextCard(animated = true) {
this.scrollToCard(this.cardIndex + this.step, animated);
}
//修改当前卡片编号
changeCardIndex(cardIndex) {
if (cardIndex == this.cardIndex) return;
this.cardIndex = cardIndex;
let total = this.pageCount;
let pageIndex = this.cycle ? cardIndex - 1 : cardIndex;
if (pageIndex < 0) pageIndex = total - 1;
else if (pageIndex >= total) pageIndex = 0;
this.setState({pageIndex});
this.props.onChange && this.props.onChange(pageIndex, total);
}
//横向滚动事件
onHorizontalScroll(e) {
let {width} = this.state;
let {x} = e.nativeEvent.contentOffset;
let cardIndex = Math.round(x / width);
if (this.cycle) {
if (cardIndex <= 0 && x <= 0) {
cardIndex = this.cardCount - 2;
this.scrollToCard(cardIndex, false);
} else if (cardIndex >= this.cardCount - 1 && x >= (this.cardCount - 1) * width) {
cardIndex = 1;
this.scrollToCard(cardIndex, false);
}
}
this.changeCardIndex(cardIndex);
this.setupTimer();
}
//纵向滚动事件
onVerticalScroll(e) {
let {height} = this.state;
let {y} = e.nativeEvent.contentOffset;
let cardIndex = Math.round(y / height);
if (this.cycle) {
if (cardIndex <= 0 && y <= 0) {
cardIndex = this.cardCount - 2;
this.scrollToCard(cardIndex, false);
} else if (cardIndex >= this.cardCount - 1 && y >= (this.cardCount - 1) * height) {
cardIndex = 1;
this.scrollToCard(cardIndex, false);
}
}
this.changeCardIndex(cardIndex);
this.setupTimer();
}
//页面滚动事件
onScroll(e) {
if (this.state.width == 0 || this.state.height == 0) return;
this.props.horizontal ? this.onHorizontalScroll(e) : this.onVerticalScroll(e);
this.props.onScroll && this.props.onScroll(e);
}
//布局变更时修改页面宽度、高度,刷新显示
onLayout(e) {
this.setState({
width: e.nativeEvent.layout.width,
height: e.nativeEvent.layout.height,
});
this.props.onLayout && this.props.onLayout(e);
}
//渲染卡片列表
renderCards() {
let {width, height} = this.state;
let {children} = this.props;
if (width <= 0 || height <= 0 || !children) return null;
if (!(children instanceof Array)) children = [children];
let cards = [];
let cardStyle = {width: width, height: height, overflow: 'hidden'};
this.cycle && cards.push(
<View style={cardStyle} key={'card-head'}>{children[children.length - 1]}</View>
);
children.map((item, index) => cards.push(
<View style={cardStyle} key={'card' + index}>{item}</View>
));
this.cycle && cards.push(
<View style={cardStyle} key={'card-tail'}>{children[0]}</View>
);
return cards;
}
render() {
let {style, children, horizontal, contentContainerStyle, control, onScroll, onLayout, direction, ...others} = this.props;
let {width, height, pageIndex} = this.state;
if (width > 0 && height > 0) {
let fixStyle;
if (horizontal) fixStyle = {width: width * this.cardCount, height: height};
else fixStyle = {width: width, height: height * this.cardCount};
contentContainerStyle = [].concat(contentContainerStyle).concat(fixStyle);
}
if (React.isValidElement(control)) {
control = React.cloneElement(control, {index: pageIndex, total: this.pageCount, carousel: this});
} else if (control) {
control = <this.constructor.Control index={pageIndex} total={this.pageCount} carousel={this} />
}
return (
<View style={[style, {alignItems: 'stretch'}]}>
<ScrollView
style={{flex: 1}}
horizontal={horizontal}
contentContainerStyle={contentContainerStyle}
{...others}
ref='scrollView'
onScroll={(e) => this.onScroll(e)}
onLayout={(e) => this.onLayout(e)}
>
{this.renderCards()}
</ScrollView>
{control}
</View>
);
}
}