react-native-insta-story-2
Version:
custom patched version of react-native-insta-story Story component for React Native.
286 lines (254 loc) • 8.74 kB
JavaScript
import React from 'react';
import PropTypes from 'prop-types';
import {
PanResponder,
Animated,
Dimensions,
StyleSheet,
Platform,
LogBox
} from 'react-native';
const {width, height} = Dimensions.get('window');
const PESPECTIVE = 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({
onMoveShouldSetResponderCapture: () => Math.abs(gestureState.dx) > 20,
onMoveShouldSetPanResponderCapture: (evt, gestureState) =>
Math.abs(gestureState.dx) > 20,
onPanResponderGrant: (e, gestureState) => {
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);
}
}
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) / PESPECTIVE,
0,
(width + 1) / PESPECTIVE,
+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={[{flex: 1}]}
ref={view => {
this._scrollView = view;
}}
{...this._panResponder.panHandlers}
>
<Animated.View
style={[
{backgroundColor: '#000', position: 'absolute', width, height},
expandStyle,
]}
>
{this.props.children.map(this._renderChild)}
</Animated.View>
</Animated.View>
);
}
}
AndroidCubeEffect.propTypes = {
callBackAfterSwipe: PropTypes.func,
scrollLockPage: PropTypes.number,
expandView: PropTypes.bool
};
AndroidCubeEffect.defaultProps = {
expandView: false
};