UNPKG

custom-react-native-stories

Version:
296 lines (263 loc) 7.22 kB
import React from 'react'; import PropTypes from 'prop-types'; import { PanResponder, Animated, Dimensions, StyleSheet, Platform, } from 'react-native'; const { width, height } = Dimensions.get('window'); const PERSPECTIVE = Platform.OS === 'ios' ? 2.38 : 2.2; const TR_POSITION = Platform.OS === 'ios' ? 2 : 1.4; export default class AndroidCubeEffect extends React.Component { constructor(props) { super(props); this.pages = this.props.children.map((child, index) => width * -index); this.fullWidth = (this.props.children.length - 1) * width; this.state = { currentPage: 0, scrollLockPage: this.pages[this.props.scrollLockPage], }; } UNSAFE_componentWillMount() { this._animatedValue = new Animated.ValueXY(); this._animatedValue.setValue({ x: 0, y: 0 }); this._value = { x: 0, y: 0 }; this._animatedValue.addListener((value) => { this._value = value; }); this._panResponder = PanResponder.create({ onMoveShouldSetPanResponderCapture: (evt, gestureState) => Math.abs(gestureState.dx) > 20, onPanResponderGrant: () => { if (this.props.callbackOnSwipe) { this.props.callbackOnSwipe(true); } this._animatedValue.stopAnimation(); this._animatedValue.setOffset({ x: this._value.x, y: this._value.y }); }, onPanResponderMove: (e, gestureState) => { if (this.props.loop) { if (gestureState.dx < 0 && this._value.x < -this.fullWidth) { this._animatedValue.setOffset({ x: width }); } else if (gestureState.dx > 0 && this._value.x > 0) { this._animatedValue.setOffset({ x: -(this.fullWidth + width) }); } } Animated.event([null, { dx: this._animatedValue.x }], { useNativeDriver: false, })(e, gestureState); }, onPanResponderRelease: (e, gestureState) => { onDoneSwiping(gestureState); }, onPanResponderTerminate: (e, gestureState) => { onDoneSwiping(gestureState); }, }); const onDoneSwiping = (gestureState) => { if (this.props.callbackOnSwipe) { this.props.callbackOnSwipe(false); } let mod = 0; if (gestureState.dx > 50) { mod = width / 2; } else if (gestureState.dx < -50) { mod = -width / 2; } let modPage = gestureState.dx > 0 ? 100 : -100; const currentPage = Math.abs(this._closestPage(this._value.x + modPage)); let goTo = this._closest(this._value.x + mod); this._animatedValue.flattenOffset({ x: this._value.x, y: this._value.y, }); Animated.spring(this._animatedValue, { toValue: { x: goTo, y: 0 }, friction: 5, tension: 0.6, useNativeDriver: false, }).start(); setTimeout(() => { this.setState({ currentPage, }); if (this.props.callBackAfterSwipe) { this.props.callBackAfterSwipe(currentPage); } }, 500); }; } UNSAFE_componentWillReceiveProps(props) { this.setState({ scrollLockPage: props.scrollLockPage ? this.pages[props.scrollLockPage] : undefined, }); } /* @page: index */ scrollTo(page, animated) { animated = animated == undefined ? true : animated; if (animated) { Animated.spring(this._animatedValue, { toValue: { x: this.pages[page], y: 0 }, friction: 5, tension: 0.6, useNativeDriver: false, }).start(); } else { this._animatedValue.setValue({ x: this.pages[page], y: 0 }); } this.setState({ currentPage: page, }); } /* Private methods */ _getTransformsFor = (i) => { let scrollX = this._animatedValue.x; let pageX = -width * i; let translateX = scrollX.interpolate({ inputRange: [pageX - width, pageX, pageX + width], outputRange: [(-width - 1) / TR_POSITION, 0, (width + 1) / TR_POSITION], extrapolate: 'clamp', }); let rotateY = scrollX.interpolate({ inputRange: [pageX - width, pageX, pageX + width], outputRange: ['-60deg', '0deg', '60deg'], extrapolate: 'clamp', }); let translateXAfterRotate = scrollX.interpolate({ inputRange: [ pageX - width, pageX - width + 0.1, pageX, pageX + width - 0.1, pageX + width, ], outputRange: [ -width - 1, (-width - 1) / PERSPECTIVE, 0, (width + 1) / PERSPECTIVE, +width + 1, ], extrapolate: 'clamp', }); let opacity = scrollX.interpolate({ inputRange: [ pageX - width, pageX - width + 10, pageX, pageX + width - 250, pageX + width, ], outputRange: [0, 0.6, 1, 0.6, 0], extrapolate: 'clamp', }); return { transform: [ { perspective: width }, { translateX }, { rotateY: rotateY }, { translateX: translateXAfterRotate }, ], opacity: opacity, }; }; _renderChild = (child, i) => { let expandStyle = this.props.expandView ? { paddingTop: 100, paddingBottom: 100, height: height + 200 } : { width, height }; let style = [child.props.style, expandStyle]; let props = { i, style, }; let element = React.cloneElement(child, props); return ( <Animated.View style={[ StyleSheet.absoluteFill, { backgroundColor: 'transparent' }, this._getTransformsFor(i, false), ]} key={`child- ${i}`} > {element} </Animated.View> ); }; _closest = (num) => { let array = this.pages; let i = 0; let minDiff = 1000; let ans; for (i in array) { let m = Math.abs(num - array[i]); if (m < minDiff) { minDiff = m; ans = array[i]; } } return ans; }; _closestPage = (num) => { let array = this.pages; let i = 0; let minDiff = 1000; let ans; for (i in array) { let m = Math.abs(num - array[i]); if (m < minDiff) { minDiff = m; ans = i; } } return ans; }; render() { let expandStyle = this.props.expandView ? { top: -100, left: 0, width, height: height + 200 } : { width, height }; return ( <Animated.View style={styles.flex} ref={(view) => { this._scrollView = view; }} {...this._panResponder.panHandlers} > <Animated.View style={[styles.blackFullScreen, expandStyle]}> {this.props.children.map(this._renderChild)} </Animated.View> </Animated.View> ); } } const styles = StyleSheet.create({ flex: { flex: 1, }, blackFullScreen: { backgroundColor: '#000', position: 'absolute', width, height, }, }); AndroidCubeEffect.propTypes = { callBackAfterSwipe: PropTypes.func, scrollLockPage: PropTypes.number, expandView: PropTypes.bool, children: PropTypes.array, callbackOnSwipe: PropTypes.func, loop: PropTypes.bool, }; AndroidCubeEffect.defaultProps = { expandView: false, };