react-native-1app
Version:
346 lines (298 loc) • 9.49 kB
JavaScript
'use strict';
var React = require('react');
var PropTypes = require('prop-types');
var createReactClass = require('create-react-class');
var ReactNative = require('react-native');
var {
Dimensions,
Text,
View,
TouchableOpacity,
PanResponder,
Animated,
StyleSheet,
} = ReactNative;
var StaticRenderer = require('react-native/Libraries/Components/StaticRenderer');
var TimerMixin = require('react-timer-mixin');
var DefaultViewPageIndicator = require('./DefaultViewPageIndicator');
var deviceWidth = Dimensions.get('window').width;
var ViewPagerDataSource = require('./ViewPagerDataSource');
var ViewPager = createReactClass({
mixins: [TimerMixin],
statics: {
DataSource: ViewPagerDataSource,
},
propTypes: {
...View.propTypes,
dataSource: PropTypes.instanceOf(ViewPagerDataSource).isRequired,
renderPage: PropTypes.func.isRequired,
onChangePage: PropTypes.func,
renderPageIndicator: PropTypes.oneOfType([
PropTypes.func,
PropTypes.bool
]),
isLoop: PropTypes.bool,
locked: PropTypes.bool,
autoPlay: PropTypes.bool,
animation: PropTypes.func,
initialPage: PropTypes.number,
},
fling: false,
getDefaultProps() {
return {
isLoop: false,
locked: false,
animation: function(animate, toValue, gs) {
return Animated.spring(animate,
{
toValue: toValue,
friction: 10,
tension: 50,
})
},
}
},
getInitialState() {
return {
currentPage: 0,
viewWidth: 0,
scrollValue: new Animated.Value(0)
};
},
componentWillMount() {
this.childIndex = 0;
var release = (e, gestureState) => {
var relativeGestureDistance = gestureState.dx / deviceWidth,
//lastPageIndex = this.props.children.length - 1,
vx = gestureState.vx;
var step = 0;
if (relativeGestureDistance < -0.5 || (relativeGestureDistance < 0 && vx <= -1e-6)) {
step = 1;
} else if (relativeGestureDistance > 0.5 || (relativeGestureDistance > 0 && vx >= 1e-6)) {
step = -1;
}
this.props.hasTouch && this.props.hasTouch(false);
this.movePage(step, gestureState);
}
this._panResponder = PanResponder.create({
// Claim responder if it's a horizontal pan
onMoveShouldSetPanResponder: (e, gestureState) => {
if (Math.abs(gestureState.dx) > Math.abs(gestureState.dy)) {
if (/* (gestureState.moveX <= this.props.edgeHitWidth ||
gestureState.moveX >= deviceWidth - this.props.edgeHitWidth) && */
this.props.locked !== true && !this.fling) {
this.props.hasTouch && this.props.hasTouch(true);
return true;
}
}
},
// Touch is released, scroll to the one that you're closest to
onPanResponderRelease: release,
onPanResponderTerminate: release,
// Dragging, move the view with the touch
onPanResponderMove: (e, gestureState) => {
var dx = gestureState.dx;
var offsetX = -dx / this.state.viewWidth + this.childIndex;
this.state.scrollValue.setValue(offsetX);
},
});
if (this.props.isLoop) {
this.childIndex = 1;
this.state.scrollValue.setValue(1);
}
if(this.props.initialPage){
var initialPage = Number(this.props.initialPage);
if (initialPage > 0) {
this.goToPage(initialPage, false);
}
}
},
componentDidMount() {
if (this.props.autoPlay) {
this._startAutoPlay();
}
},
componentWillReceiveProps(nextProps) {
if (nextProps.autoPlay) {
this._startAutoPlay();
} else {
if (this._autoPlayer) {
this.clearInterval(this._autoPlayer);
this._autoPlayer = null;
}
}
if (nextProps.dataSource) {
var maxPage = nextProps.dataSource.getPageCount() - 1;
var constrainedPage = Math.max(0, Math.min(this.state.currentPage, maxPage));
this.setState({
currentPage: constrainedPage,
});
if (!nextProps.isLoop) {
this.state.scrollValue.setValue(constrainedPage > 0 ? 1 : 0);
}
this.childIndex = Math.min(this.childIndex, constrainedPage);
this.fling = false;
}
},
_startAutoPlay() {
if (!this._autoPlayer) {
this._autoPlayer = this.setInterval(
() => {this.movePage(1);},
5000
);
}
},
goToPage(pageNumber, animate = true) {
var pageCount = this.props.dataSource.getPageCount();
if (pageNumber < 0 || pageNumber >= pageCount) {
console.error('Invalid page number: ', pageNumber);
return
}
var step = pageNumber - this.state.currentPage;
this.movePage(step, null, animate);
},
movePage(step, gs, animate = true) {
var pageCount = this.props.dataSource.getPageCount();
var pageNumber = this.state.currentPage + step;
if (this.props.isLoop) {
pageNumber = pageCount == 0 ? pageNumber = 0 : ((pageNumber + pageCount) % pageCount);
} else {
pageNumber = Math.min(Math.max(0, pageNumber), pageCount - 1);
}
const moved = pageNumber !== this.state.currentPage;
const scrollStep = (moved ? step : 0) + this.childIndex;
const nextChildIdx = (pageNumber > 0 || this.props.isLoop) ? 1 : 0;
const postChange = () => {
this.fling = false;
this.childIndex = nextChildIdx;
this.state.scrollValue.setValue(nextChildIdx);
this.setState({
currentPage: pageNumber,
});
};
if (animate) {
this.fling = true;
this.props.animation(this.state.scrollValue, scrollStep, gs)
.start((event) => {
if (event.finished) {
postChange();
}
moved && this.props.onChangePage && this.props.onChangePage(pageNumber);
});
} else {
postChange();
moved && this.props.onChangePage && this.props.onChangePage(pageNumber);
}
},
getCurrentPage() {
return this.state.currentPage;
},
renderPageIndicator(props) {
if (this.props.renderPageIndicator === false) {
return null;
} else if (this.props.renderPageIndicator) {
return React.cloneElement(this.props.renderPageIndicator(), props);
} else {
return (
<View style={styles.indicators}>
<DefaultViewPageIndicator {...props} />
</View>
);
}
},
_getPage(pageIdx: number, loop:boolean) {
var dataSource = this.props.dataSource;
var pageID = dataSource.pageIdentities[pageIdx];
return (
<StaticRenderer
key={'p_' + pageID + (loop ? '_1' : '')}
shouldUpdate={true}
render={this.props.renderPage.bind(
null,
dataSource.getPageData(pageIdx),
pageID,
this.state.currentPage
)}
/>
);
},
render() {
var dataSource = this.props.dataSource;
var pageIDs = dataSource.pageIdentities;
var bodyComponents = [];
var pagesNum = 0;
var hasLeft = false;
var viewWidth = this.state.viewWidth;
if(pageIDs.length > 0 && viewWidth > 0) {
// left page
if (this.state.currentPage > 0) {
bodyComponents.push(this._getPage(this.state.currentPage - 1));
pagesNum++;
hasLeft = true;
} else if (this.state.currentPage == 0 && this.props.isLoop) {
bodyComponents.push(this._getPage(pageIDs.length - 1, true));
pagesNum++;
hasLeft = true;
}
// center page
bodyComponents.push(this._getPage(this.state.currentPage));
pagesNum++;
// right page
if (this.state.currentPage < pageIDs.length - 1) {
bodyComponents.push(this._getPage(this.state.currentPage + 1));
pagesNum++;
} else if (this.state.currentPage == pageIDs.length - 1 && this.props.isLoop) {
bodyComponents.push(this._getPage(0, true));
pagesNum++;
}
}
var sceneContainerStyle = {
width: viewWidth * pagesNum,
flex: 1,
flexDirection: 'row'
};
// this.childIndex = hasLeft ? 1 : 0;
// this.state.scrollValue.setValue(this.childIndex);
var translateX = this.state.scrollValue.interpolate({
inputRange: [0, 1], outputRange: [0, -viewWidth]
});
return (
<View style={{flex: 1}}
onLayout={(event) => {
// console.log('ViewPager.onLayout()');
var viewWidth = event.nativeEvent.layout.width;
if (!viewWidth || this.state.viewWidth === viewWidth) {
return;
}
this.setState({
currentPage: this.state.currentPage,
viewWidth: viewWidth,
});
}}
>
<Animated.View style={[sceneContainerStyle, {transform: [{translateX}]}]}
{...this._panResponder.panHandlers}>
{bodyComponents}
</Animated.View>
{this.renderPageIndicator({goToPage: this.goToPage,
pageCount: pageIDs.length,
activePage: this.state.currentPage,
scrollValue: this.state.scrollValue,
scrollOffset: this.childIndex,
})}
</View>
);
}
});
var styles = StyleSheet.create({
indicators: {
flex: 1,
alignItems: 'center',
position: 'absolute',
bottom: 10,
left: 0,
right: 0,
backgroundColor: 'transparent',
},
});
module.exports = ViewPager;