license import React from 'react' ; import { Animated , Easing , GestureResponderEvent , LayoutChangeEvent , PanResponder , PanResponderCallbacks , PanResponderGestureState , PanResponderInstance , Platform , StyleSheet , View , ViewProps , ViewStyle , } from 'react-native' ; import { ChildrenWithProps , RTLService , } from '../../devsupport' ; export interface ViewPagerProps < ChildrenProps = { } > extends ViewProps { children ? : ChildrenWithProps < ChildrenProps > ; selectedIndex ? : number ; swipeEnabled ? : boolean ; onSelect ? : ( index : number ) => void ; shouldLoadComponent ? : ( index : number ) => boolean ; onOffsetChange ? : ( offset : number ) => void ; } export type ViewPagerElement = React . ReactElement < ViewPagerProps > ; extends React . Component property property property property property property property overview-example overview-example export class ViewPager < ChildrenProps = { } > extends React . Component < ViewPagerProps < ChildrenProps > > implements PanResponderCallbacks { static defaultProps : Partial < ViewPagerProps > = { selectedIndex : 0 , swipeEnabled : true , shouldLoadComponent : ( ) : boolean => true , } ; private containerRef = React . createRef < View > ( ) ; private contentWidth : number = 0 ; private contentOffsetValue : number = 0 ; private contentOffset : Animated . Value = new Animated . Value ( this . contentOffsetValue ) ; private panResponder : PanResponderInstance = PanResponder . create ( this ) ; private get children ( ) : React . ReactElement < ChildrenProps > [ ] { return React . Children . toArray ( this . props . children ) . filter ( Boolean ) as React . ReactElement < ChildrenProps > [ ] ; } public componentDidMount ( ) { this . contentOffset . addListener ( this . onContentOffsetAnimationStateChanged ) ; } public componentDidUpdate ( prevProps ) { if ( prevProps . selectedIndex !== this . props . selectedIndex ) { const index = this . props . selectedIndex ; this . scrollToIndex ( { index , animated : true } ) ; } } public componentWillUnmount ( ) { this . contentOffset . removeAllListeners ( ) ; } public onMoveShouldSetPanResponder = ( _event , state ) : boolean => { const isHorizontalMove = Math . abs ( state . dx ) > 0 && Math . abs ( state . dx ) > Math . abs ( state . dy ) ; if ( isHorizontalMove ) { const i18nOffset = RTLService . select ( state . dx , - state . dx ) ; const nextSelectedIndex = this . props . selectedIndex - Math . sign ( i18nOffset ) ; return nextSelectedIndex >= 0 && nextSelectedIndex < this . children . length ; } return false ; } ; public onPanResponderMove = ( _event , state ) : void => { const i18nOffset = RTLService . select ( this . contentWidth , - this . contentWidth ) ; const selectedPageOffset = this . props . selectedIndex * i18nOffset ; this . contentOffset . setValue ( state . dx - selectedPageOffset ) ; } ; public onPanResponderRelease = ( event , state ) => { if ( Math . abs ( state . vx ) >= 0.5 || Math . abs ( state . dx ) >= 0.5 * this . contentWidth ) { const i18nOffset = RTLService . select ( state . dx , - state . dx ) ; const index = i18nOffset > 0 ? this . props . selectedIndex - 1 : this . props . selectedIndex + 1 ; this . scrollToIndex ( { index , animated : true } ) ; } else { const index = this . props . selectedIndex ; this . scrollToIndex ( { index , animated : true } ) ; } } ; public scrollToIndex ( params ) { const { index , ... rest } = params ; const childCount = this . children . length - 1 ; const offset = this . contentWidth * ( index < 0 ? 0 : index > childCount ? childCount : index ) ; this . scrollToOffset ( { offset , ... rest } ) ; } public scrollToOffset = ( params ) : void => { this . createOffsetAnimation ( params ) . start ( this . onContentOffsetAnimationStateEnd ) ; } ; private onLayout = ( event ) : void => { this . contentWidth = event . nativeEvent . layout . width / this . children . length ; this . scrollToIndex ( { index : this . props . selectedIndex } ) ; } ; private onContentOffsetAnimationStateChanged = ( state ) : void => { this . contentOffsetValue = RTLService . select ( - state . value , state . value ) ; if ( this . props . onOffsetChange ) { this . props . onOffsetChange ( this . contentOffsetValue ) ; } } ; private onContentOffsetAnimationStateEnd = ( _result ) : void => { const selectedIndex = this . contentOffsetValue / this . contentWidth ; if ( selectedIndex !== this . props . selectedIndex && this . props . onSelect ) { this . props . onSelect ( Math . round ( selectedIndex ) ) ; } } ; private createOffsetAnimation = ( params ) : Animated . CompositeAnimation => { const animationDuration = params . animated ? 300 : 0 ; return Animated . timing ( this . contentOffset , { toValue : RTLService . select ( - params . offset , params . offset ) , easing : Easing . linear , duration : animationDuration , useNativeDriver : Platform . OS !== 'web' , } ) ; } ; private getContainerStyle = ( ) : ViewStyle => { return { width : `${ 100 * this . children . length }%` , transform : [ { translateX : this . contentOffset } ] , } ; } ; private renderComponentChild = ( source , index ) : React . ReactElement => { const contentView = this . props . shouldLoadComponent ( index ) ? source : null ; return ( < View style = { styles . contentContainer } > { contentView } < /View>); } ; private renderComponentChildren = ( ) : React . ReactElement [ ] => { return React . Children . map ( this . children , this . renderComponentChild ) ; } ; public render ( ) { const { style , children , swipeEnabled , ... viewProps } = this . props ; const panResponderConfig = swipeEnabled ? this . panResponder . panHandlers : null ; const animatedViewProps = { ... viewProps , ... panResponderConfig } ; return ( < Animated . View { ... animatedViewProps } style = { [ styles . container , style , this . getContainerStyle ( ) ] } onLayout = { this . onLayout } ref = { this . containerRef } > { this . renderComponentChildren ( ) } < /Animated.View>); } } const styles = StyleSheet . create ( { container : { flexDirection : 'row' , } , contentContainer : { flex : 1 , width : '100%' , } , } ) ;