import * as React from 'react'; import { InteractionManager, StyleSheet, View, ViewStyle, } from 'react-native'; import { IMDSwiperProps, IMDSwiperStyle, MDSwiperStyles } from './swiper.base'; import SwiperDots from './swiper.dots'; export interface IMDSwiperSlideState { index: number; isStoped: boolean; autoplay: number; ready: boolean; } const styles = StyleSheet.create(MDSwiperStyles); const addPropsToSwiperItem = ( children: any, width: number, height: number, backup: boolean, transition: string, key: any ): React.ReactNode | React.ReactNode[] => { if (!children) { return null; } if (children.props && children.props.__name === 'MDSwiperItem') { const _style: ViewStyle = { width, height, }; if (transition === 'slideY') { const _styleWrapper: ViewStyle = { height: width, width: height, justifyContent: 'center', alignItems: 'center', overflow: 'hidden', }; return ( {children} ); } return ( {children} ); } else if (React.Children.count(children) > 0) { const _children = []; // 用于循环,在最前面复制最后一条数据 backup && _children.push( addPropsToSwiperItem( children[children.length - 1], width, height, backup, transition, -1 ) ); React.Children.forEach(children, (child, index) => { _children.push( addPropsToSwiperItem(child, width, height, backup, transition, index) ); }); // 用于循环,在最后面复制第一条数据 backup && _children.push( addPropsToSwiperItem( children[0], width, height, backup, transition, children.length ) ); return _children; } return children; }; export default abstract class MDSwiperSlideIOS extends React.Component< IMDSwiperProps, IMDSwiperSlideState > { public static defaultProps = { styles, autoplay: 3000, transition: 'slide', defaultIndex: 0, hasDots: true, isLoop: true, dragable: true, }; constructor (props: IMDSwiperProps) { super(props); // @ts-ignore const { defaultIndex, autoplay } = props; // @ts-ignore const _oCount = this.getOItemCount(); const _defaultIndex = defaultIndex || 0; let index = _defaultIndex >= 0 && _defaultIndex < _oCount ? _defaultIndex : 0; if (this.isNeedBackup()) { index = index + 1; } this.fromIndex = index === 0 ? _oCount - 1 : index - 1; this.toIndex = index; this.userScrolling = false; this.state = { index, isStoped: false, autoplay: autoplay || 3000, ready: false, }; } protected fromIndex: number; protected toIndex: number; protected timer: any; protected userScrolling: boolean; public componentDidMount () { const { children } = this.props; // @ts-ignore if (!children || !children.length) { return; } InteractionManager.runAfterInteractions(() => { setTimeout(() => { this.translate(this.state.index, false); }, 100); this.setState( { ready: true, }, () => { this.startPlay(); } ); }); } public componentWillUnmount () { this.clearTimer(); } // MARK: public methods public next () { this.doTransition('next'); } public prev () { this.doTransition('prev'); } public goto (index: number) { if (isNaN(index)) { return; } const oItemCount = this.getOItemCount(); if (index === this.state.index || index < 0 || index >= oItemCount) { return; } this.clearTimer(); const towards = index > this.state.index ? 'next' : 'prev'; this.setState({ index, }); this.doTransition(towards, { index }); this.startPlay(); } public getIndex (): number { return this.calcuRealIndex(this.state.index); } public play (autoplay = 3000) { this.clearTimer(); if (autoplay < 500) { return; } this.setState({ autoplay: autoplay || this.props.autoplay || 3000, isStoped: false, }); this.startPlay(); } public stop () { this.clearTimer(); this.setState({ isStoped: true, }); } public render () { const { width, height } = this.props; const _styles = this.props.styles!; const _scrollView = this.renderScrollView(); const _dots = ( ); return ( {_scrollView} {_dots} ); } protected abstract renderScrollView (): React.ReactNode; protected abstract translate (index: number, animated: boolean): void; protected abstract onScrollEnd (e: any): void; protected renderChildren () { const { children, width, height, transition } = this.props; return addPropsToSwiperItem( children, width, height, this.isNeedBackup(), transition!, 0 ); } // MARK Calculate Method protected isFirstItem () { return this.state.index === 0; } protected isLastItem () { return this.state.index === this.getRItemCount() - 1; } protected originalIndex () { if (this.props.isLoop) { return this.state.index - 1; } else { return this.state.index; } } protected isVertical () { return this.props.transition === 'slideY'; } protected getDimension () { return this.isVertical() ? this.props.height : this.props.width; } protected getOItemCount () { const { children } = this.props; return children ? React.Children.count(children) || 1 : 0; } protected getRItemCount () { const { isLoop } = this.props; const oItemCount = this.getOItemCount(); if (!isLoop) { // 不循环:没有前后的加帧,返回原始count return oItemCount; } if (oItemCount <= 1) { // 循环,但原始count只有一个:没有前后的加帧,返回原始count return oItemCount; } // 循环,且原始count大于1:有前后的加帧,返回原始count + 2 return oItemCount + 2; } protected isNeedBackup (): boolean { return this.getOItemCount() < this.getRItemCount(); } protected getFirstIndex (): number { if (this.isNeedBackup()) { return 1; } return 0; } protected getLastIndex (): number { if (this.isNeedBackup()) { return this.getOItemCount(); } return this.getOItemCount() - 1; } protected getIsNotDraggable (): boolean { return this.getOItemCount() === 1 || !this.props.dragable; } // MARK logic Method protected startPlay () { const { autoplay, isLoop } = this.props; const { index } = this.state; const oItemCount = this.getOItemCount(); const rItemCount = this.getRItemCount(); if (autoplay! > 0 && oItemCount > 1 && isLoop) { const _timer = setInterval(() => { if (!isLoop && index >= rItemCount - 1) { return this.clearTimer(); } this.doTransition('next'); }, this.state.autoplay); this.timer = _timer; } } protected clearTimer () { if (this.timer) { clearInterval(this.timer); this.timer = null; } } protected calcuRealIndex (index: number) { const oItemCount = this.getOItemCount(); if (this.props.isLoop && oItemCount > 0) { return index - 1 < 0 ? oItemCount - 1 : index - 1 > oItemCount - 1 ? 0 : index - 1; } return index; } protected calcuNewIndex ( towards: string, options?: any ): { newFromIndex: number; newToIndex: number; newIndex: number } { const { isLoop } = this.props; const { index } = this.state; const { fromIndex, toIndex } = this; const oItemCount = this.getOItemCount(); const rItemCount = this.getRItemCount(); const result = { newFromIndex: fromIndex, newToIndex: toIndex, newIndex: index, }; if (oItemCount === 0) { return result; } if (!options && oItemCount < 2) { return result; } const itemCount = rItemCount; let newIndex = index; const oldIndex = index; if (!towards) { return result; } if (options && options.index) { newIndex = options.index + (isLoop ? 1 : 0); } else if (towards === 'prev') { if (index > 0) { newIndex = index - 1; } else if (isLoop && index === 0) { newIndex = itemCount - 1; } } else if (towards === 'next') { if (index < itemCount - 1) { newIndex = index + 1; } else if (isLoop && index === itemCount - 1) { newIndex = 1; } } let newFromIndex = toIndex; let newToIndex = newIndex; if (isLoop) { newFromIndex = this.calcuRealIndex(oldIndex); newToIndex = this.calcuRealIndex(newIndex); } return { newFromIndex, newToIndex, newIndex }; } protected doTransition (towards: string, options?: any) { const { onBeforeChange } = this.props; const { newFromIndex, newToIndex, newIndex } = this.calcuNewIndex( towards, options ); this.fromIndex = newFromIndex; this.toIndex = newToIndex; this.setState( { index: newIndex, }, () => { onBeforeChange && onBeforeChange(newFromIndex, newToIndex); this.translate(newIndex, true); } ); } protected afterTrans () { const { isLoop, onAfterChange } = this.props; const { fromIndex, toIndex } = this; const firstIndex = this.getFirstIndex(); const lastIndex = this.getLastIndex(); if (isLoop && (this.isLastItem() || this.isFirstItem())) { const _index = this.isLastItem() ? firstIndex : lastIndex; this.setState( { index: _index, }, () => { this.translate(_index, false); } ); } onAfterChange && onAfterChange(fromIndex, toIndex); } }