diff --git a/src/carousel/Carousel.js b/src/carousel/Carousel.js
index dae71a3da..7cca740aa 100644
--- a/src/carousel/Carousel.js
+++ b/src/carousel/Carousel.js
@@ -1,22 +1,34 @@
-import React, { Component } from 'react';
-import { Animated, Easing, FlatList, I18nManager, Platform, ScrollView, View, ViewPropTypes } from 'react-native';
-import PropTypes from 'prop-types';
-import shallowCompare from 'react-addons-shallow-compare';
+import React, { Component } from "react";
import {
- defaultScrollInterpolator,
- stackScrollInterpolator,
- tinderScrollInterpolator,
- defaultAnimatedStyles,
- shiftAnimatedStyles,
- stackAnimatedStyles,
- tinderAnimatedStyles
-} from '../utils/animations';
+ Animated,
+ Easing,
+ FlatList,
+ I18nManager,
+ Platform,
+ ScrollView,
+ View,
+} from "react-native";
+import { ViewPropTypes } from "deprecated-react-native-prop-types";
+
+import PropTypes from "prop-types";
+import shallowCompare from "react-addons-shallow-compare";
+import {
+ defaultScrollInterpolator,
+ stackScrollInterpolator,
+ tinderScrollInterpolator,
+ defaultAnimatedStyles,
+ shiftAnimatedStyles,
+ stackAnimatedStyles,
+ tinderAnimatedStyles,
+} from "../utils/animations";
-const IS_IOS = Platform.OS === 'ios';
+const IS_IOS = Platform.OS === "ios";
// Native driver for scroll events
// See: https://facebook.github.io/react-native/blog/2017/02/14/using-native-driver-for-animated.html
-const AnimatedFlatList = FlatList ? Animated.createAnimatedComponent(FlatList) : null;
+const AnimatedFlatList = FlatList
+ ? Animated.createAnimatedComponent(FlatList)
+ : null;
const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView);
// React Native automatically handles RTL layouts; unfortunately, it's buggy with horizontal ScrollView
@@ -26,1346 +38,1516 @@ const AnimatedScrollView = Animated.createAnimatedComponent(ScrollView);
const IS_RTL = I18nManager.isRTL;
export default class Carousel extends Component {
-
- static propTypes = {
- data: PropTypes.array.isRequired,
- renderItem: PropTypes.func.isRequired,
- itemWidth: PropTypes.number, // required for horizontal carousel
- itemHeight: PropTypes.number, // required for vertical carousel
- sliderWidth: PropTypes.number, // required for horizontal carousel
- sliderHeight: PropTypes.number, // required for vertical carousel
- activeAnimationType: PropTypes.string,
- activeAnimationOptions: PropTypes.object,
- activeSlideAlignment: PropTypes.oneOf(['center', 'end', 'start']),
- activeSlideOffset: PropTypes.number,
- apparitionDelay: PropTypes.number,
- autoplay: PropTypes.bool,
- autoplayDelay: PropTypes.number,
- autoplayInterval: PropTypes.number,
- callbackOffsetMargin: PropTypes.number,
- containerCustomStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
- contentContainerCustomStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
- enableMomentum: PropTypes.bool,
- enableSnap: PropTypes.bool,
- firstItem: PropTypes.number,
- hasParallaxImages: PropTypes.bool,
- inactiveSlideOpacity: PropTypes.number,
- inactiveSlideScale: PropTypes.number,
- inactiveSlideShift: PropTypes.number,
- layout: PropTypes.oneOf(['default', 'stack', 'tinder']),
- layoutCardOffset: PropTypes.number,
- lockScrollTimeoutDuration: PropTypes.number,
- lockScrollWhileSnapping: PropTypes.bool,
- loop: PropTypes.bool,
- loopClonesPerSide: PropTypes.number,
- scrollEnabled: PropTypes.bool,
- scrollInterpolator: PropTypes.func,
- slideInterpolatedStyle: PropTypes.func,
- slideStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
- shouldOptimizeUpdates: PropTypes.bool,
- swipeThreshold: PropTypes.number,
- useScrollView: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
- vertical: PropTypes.bool,
- onBeforeSnapToItem: PropTypes.func,
- onSnapToItem: PropTypes.func
+ static propTypes = {
+ data: PropTypes.array.isRequired,
+ renderItem: PropTypes.func.isRequired,
+ itemWidth: PropTypes.number, // required for horizontal carousel
+ itemHeight: PropTypes.number, // required for vertical carousel
+ sliderWidth: PropTypes.number, // required for horizontal carousel
+ sliderHeight: PropTypes.number, // required for vertical carousel
+ activeAnimationType: PropTypes.string,
+ activeAnimationOptions: PropTypes.object,
+ activeSlideAlignment: PropTypes.oneOf(["center", "end", "start"]),
+ activeSlideOffset: PropTypes.number,
+ apparitionDelay: PropTypes.number,
+ autoplay: PropTypes.bool,
+ autoplayDelay: PropTypes.number,
+ autoplayInterval: PropTypes.number,
+ callbackOffsetMargin: PropTypes.number,
+ containerCustomStyle: ViewPropTypes
+ ? ViewPropTypes.style
+ : View.propTypes.style,
+ contentContainerCustomStyle: ViewPropTypes
+ ? ViewPropTypes.style
+ : View.propTypes.style,
+ enableMomentum: PropTypes.bool,
+ enableSnap: PropTypes.bool,
+ firstItem: PropTypes.number,
+ hasParallaxImages: PropTypes.bool,
+ inactiveSlideOpacity: PropTypes.number,
+ inactiveSlideScale: PropTypes.number,
+ inactiveSlideShift: PropTypes.number,
+ layout: PropTypes.oneOf(["default", "stack", "tinder"]),
+ layoutCardOffset: PropTypes.number,
+ lockScrollTimeoutDuration: PropTypes.number,
+ lockScrollWhileSnapping: PropTypes.bool,
+ loop: PropTypes.bool,
+ loopClonesPerSide: PropTypes.number,
+ scrollEnabled: PropTypes.bool,
+ scrollInterpolator: PropTypes.func,
+ slideInterpolatedStyle: PropTypes.func,
+ slideStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
+ shouldOptimizeUpdates: PropTypes.bool,
+ swipeThreshold: PropTypes.number,
+ useScrollView: PropTypes.oneOfType([PropTypes.bool, PropTypes.func]),
+ vertical: PropTypes.bool,
+ onBeforeSnapToItem: PropTypes.func,
+ onSnapToItem: PropTypes.func,
+ };
+
+ static defaultProps = {
+ activeAnimationType: "timing",
+ activeAnimationOptions: null,
+ activeSlideAlignment: "center",
+ activeSlideOffset: 20,
+ apparitionDelay: 0,
+ autoplay: false,
+ autoplayDelay: 1000,
+ autoplayInterval: 3000,
+ callbackOffsetMargin: 5,
+ containerCustomStyle: {},
+ contentContainerCustomStyle: {},
+ enableMomentum: false,
+ enableSnap: true,
+ firstItem: 0,
+ hasParallaxImages: false,
+ inactiveSlideOpacity: 0.7,
+ inactiveSlideScale: 0.9,
+ inactiveSlideShift: 0,
+ layout: "default",
+ lockScrollTimeoutDuration: 1000,
+ lockScrollWhileSnapping: false,
+ loop: false,
+ loopClonesPerSide: 3,
+ scrollEnabled: true,
+ slideStyle: {},
+ shouldOptimizeUpdates: true,
+ swipeThreshold: 20,
+ useScrollView: !AnimatedFlatList,
+ vertical: false,
+ };
+
+ constructor(props) {
+ super(props);
+
+ this.state = {
+ hideCarousel: true,
+ interpolators: [],
};
- static defaultProps = {
- activeAnimationType: 'timing',
- activeAnimationOptions: null,
- activeSlideAlignment: 'center',
- activeSlideOffset: 20,
- apparitionDelay: 0,
- autoplay: false,
- autoplayDelay: 1000,
- autoplayInterval: 3000,
- callbackOffsetMargin: 5,
- containerCustomStyle: {},
- contentContainerCustomStyle: {},
- enableMomentum: false,
- enableSnap: true,
- firstItem: 0,
- hasParallaxImages: false,
- inactiveSlideOpacity: 0.7,
- inactiveSlideScale: 0.9,
- inactiveSlideShift: 0,
- layout: 'default',
- lockScrollTimeoutDuration: 1000,
- lockScrollWhileSnapping: false,
- loop: false,
- loopClonesPerSide: 3,
- scrollEnabled: true,
- slideStyle: {},
- shouldOptimizeUpdates: true,
- swipeThreshold: 20,
- useScrollView: !AnimatedFlatList,
- vertical: false
- }
-
- constructor (props) {
- super(props);
-
- this.state = {
- hideCarousel: true,
- interpolators: []
- };
-
- // The following values are not stored in the state because 'setState()' is asynchronous
- // and this results in an absolutely crappy behavior on Android while swiping (see #156)
- const initialActiveItem = this._getFirstItem(props.firstItem);
- this._activeItem = initialActiveItem;
- this._previousActiveItem = initialActiveItem;
- this._previousFirstItem = initialActiveItem;
- this._previousItemsLength = initialActiveItem;
-
- this._mounted = false;
- this._positions = [];
- this._currentContentOffset = 0; // store ScrollView's scroll position
- this._canFireBeforeCallback = false;
- this._canFireCallback = false;
- this._scrollOffsetRef = null;
- this._onScrollTriggered = true; // used when momentum is enabled to prevent an issue with edges items
- this._lastScrollDate = 0; // used to work around a FlatList bug
- this._scrollEnabled = props.scrollEnabled !== false;
-
- this._initPositionsAndInterpolators = this._initPositionsAndInterpolators.bind(this);
- this._renderItem = this._renderItem.bind(this);
- this._onSnap = this._onSnap.bind(this);
-
- this._onLayout = this._onLayout.bind(this);
- this._onScroll = this._onScroll.bind(this);
- this._onScrollBeginDrag = props.enableSnap ? this._onScrollBeginDrag.bind(this) : undefined;
- this._onScrollEnd = props.enableSnap || props.autoplay ? this._onScrollEnd.bind(this) : undefined;
- this._onScrollEndDrag = !props.enableMomentum ? this._onScrollEndDrag.bind(this) : undefined;
- this._onMomentumScrollEnd = props.enableMomentum ? this._onMomentumScrollEnd.bind(this) : undefined;
- this._onTouchStart = this._onTouchStart.bind(this);
- this._onTouchEnd = this._onTouchEnd.bind(this);
- this._onTouchRelease = this._onTouchRelease.bind(this);
-
- this._getKeyExtractor = this._getKeyExtractor.bind(this);
-
- this._setScrollHandler(props);
-
- // This bool aims at fixing an iOS bug due to scrollTo that triggers onMomentumScrollEnd.
- // onMomentumScrollEnd fires this._snapScroll, thus creating an infinite loop.
- this._ignoreNextMomentum = false;
-
- // Warnings
- if (!ViewPropTypes) {
- console.warn('react-native-snap-carousel: It is recommended to use at least version 0.44 of React Native with the plugin');
- }
- if (!props.vertical && (!props.sliderWidth || !props.itemWidth)) {
- console.error('react-native-snap-carousel: You need to specify both `sliderWidth` and `itemWidth` for horizontal carousels');
- }
- if (props.vertical && (!props.sliderHeight || !props.itemHeight)) {
- console.error('react-native-snap-carousel: You need to specify both `sliderHeight` and `itemHeight` for vertical carousels');
- }
- if (props.apparitionDelay && !IS_IOS && !props.useScrollView) {
- console.warn('react-native-snap-carousel: Using `apparitionDelay` on Android is not recommended since it can lead to rendering issues');
- }
- if (props.customAnimationType || props.customAnimationOptions) {
- console.warn('react-native-snap-carousel: Props `customAnimationType` and `customAnimationOptions` have been renamed to `activeAnimationType` and `activeAnimationOptions`');
- }
- if (props.onScrollViewScroll) {
- console.error('react-native-snap-carousel: Prop `onScrollViewScroll` has been removed. Use `onScroll` instead');
- }
+ // The following values are not stored in the state because 'setState()' is asynchronous
+ // and this results in an absolutely crappy behavior on Android while swiping (see #156)
+ const initialActiveItem = this._getFirstItem(props.firstItem);
+ this._activeItem = initialActiveItem;
+ this._previousActiveItem = initialActiveItem;
+ this._previousFirstItem = initialActiveItem;
+ this._previousItemsLength = initialActiveItem;
+
+ this._mounted = false;
+ this._positions = [];
+ this._currentContentOffset = 0; // store ScrollView's scroll position
+ this._canFireBeforeCallback = false;
+ this._canFireCallback = false;
+ this._scrollOffsetRef = null;
+ this._onScrollTriggered = true; // used when momentum is enabled to prevent an issue with edges items
+ this._lastScrollDate = 0; // used to work around a FlatList bug
+ this._scrollEnabled = props.scrollEnabled !== false;
+
+ this._initPositionsAndInterpolators =
+ this._initPositionsAndInterpolators.bind(this);
+ this._renderItem = this._renderItem.bind(this);
+ this._onSnap = this._onSnap.bind(this);
+
+ this._onLayout = this._onLayout.bind(this);
+ this._onScroll = this._onScroll.bind(this);
+ this._onScrollBeginDrag = props.enableSnap
+ ? this._onScrollBeginDrag.bind(this)
+ : undefined;
+ this._onScrollEnd =
+ props.enableSnap || props.autoplay
+ ? this._onScrollEnd.bind(this)
+ : undefined;
+ this._onScrollEndDrag = !props.enableMomentum
+ ? this._onScrollEndDrag.bind(this)
+ : undefined;
+ this._onMomentumScrollEnd = props.enableMomentum
+ ? this._onMomentumScrollEnd.bind(this)
+ : undefined;
+ this._onTouchStart = this._onTouchStart.bind(this);
+ this._onTouchEnd = this._onTouchEnd.bind(this);
+ this._onTouchRelease = this._onTouchRelease.bind(this);
+
+ this._getKeyExtractor = this._getKeyExtractor.bind(this);
+
+ this._setScrollHandler(props);
+
+ // This bool aims at fixing an iOS bug due to scrollTo that triggers onMomentumScrollEnd.
+ // onMomentumScrollEnd fires this._snapScroll, thus creating an infinite loop.
+ this._ignoreNextMomentum = false;
+
+ // Warnings
+ if (!ViewPropTypes) {
+ console.warn(
+ "react-native-snap-carousel: It is recommended to use at least version 0.44 of React Native with the plugin"
+ );
}
-
- componentDidMount () {
- const { apparitionDelay, autoplay, firstItem } = this.props;
- const _firstItem = this._getFirstItem(firstItem);
- const apparitionCallback = () => {
- this.setState({ hideCarousel: false });
- if (autoplay) {
- this.startAutoplay();
- }
- };
-
- this._mounted = true;
- this._initPositionsAndInterpolators();
-
- // Without 'requestAnimationFrame' or a `0` timeout, images will randomly not be rendered on Android...
- requestAnimationFrame(() => {
- if (!this._mounted) {
- return;
- }
-
- this._snapToItem(_firstItem, false, false, true, false);
- this._hackActiveSlideAnimation(_firstItem, 'start', true);
-
- if (apparitionDelay) {
- this._apparitionTimeout = setTimeout(() => {
- apparitionCallback();
- }, apparitionDelay);
- } else {
- apparitionCallback();
- }
- });
+ if (!props.vertical && (!props.sliderWidth || !props.itemWidth)) {
+ console.error(
+ "react-native-snap-carousel: You need to specify both `sliderWidth` and `itemWidth` for horizontal carousels"
+ );
}
-
- shouldComponentUpdate (nextProps, nextState) {
- if (this.props.shouldOptimizeUpdates === false) {
- return true;
- } else {
- return shallowCompare(this, nextProps, nextState);
- }
+ if (props.vertical && (!props.sliderHeight || !props.itemHeight)) {
+ console.error(
+ "react-native-snap-carousel: You need to specify both `sliderHeight` and `itemHeight` for vertical carousels"
+ );
}
+ if (props.apparitionDelay && !IS_IOS && !props.useScrollView) {
+ console.warn(
+ "react-native-snap-carousel: Using `apparitionDelay` on Android is not recommended since it can lead to rendering issues"
+ );
+ }
+ if (props.customAnimationType || props.customAnimationOptions) {
+ console.warn(
+ "react-native-snap-carousel: Props `customAnimationType` and `customAnimationOptions` have been renamed to `activeAnimationType` and `activeAnimationOptions`"
+ );
+ }
+ if (props.onScrollViewScroll) {
+ console.error(
+ "react-native-snap-carousel: Prop `onScrollViewScroll` has been removed. Use `onScroll` instead"
+ );
+ }
+ }
- componentDidUpdate (prevProps) {
- const { interpolators } = this.state;
- const { firstItem, itemHeight, itemWidth, scrollEnabled, sliderHeight, sliderWidth } = this.props;
- const itemsLength = this._getCustomDataLength(this.props);
-
- if (!itemsLength) {
- return;
- }
+ componentDidMount() {
+ const { apparitionDelay, autoplay, firstItem } = this.props;
+ const _firstItem = this._getFirstItem(firstItem);
+ const apparitionCallback = () => {
+ this.setState({ hideCarousel: false });
+ if (autoplay) {
+ this.startAutoplay();
+ }
+ };
- const nextFirstItem = this._getFirstItem(firstItem, this.props);
- let nextActiveItem = this._activeItem || this._activeItem === 0 ? this._activeItem : nextFirstItem;
+ this._mounted = true;
+ this._initPositionsAndInterpolators();
- const hasNewSliderWidth = sliderWidth && sliderWidth !== prevProps.sliderWidth;
- const hasNewSliderHeight = sliderHeight && sliderHeight !== prevProps.sliderHeight;
- const hasNewItemWidth = itemWidth && itemWidth !== prevProps.itemWidth;
- const hasNewItemHeight = itemHeight && itemHeight !== prevProps.itemHeight;
- const hasNewScrollEnabled = scrollEnabled !== prevProps.scrollEnabled;
+ // Without 'requestAnimationFrame' or a `0` timeout, images will randomly not be rendered on Android...
+ requestAnimationFrame(() => {
+ if (!this._mounted) {
+ return;
+ }
- // Prevent issues with dynamically removed items
- if (nextActiveItem > itemsLength - 1) {
- nextActiveItem = itemsLength - 1;
- }
+ this._snapToItem(_firstItem, false, false, true, false);
+ this._hackActiveSlideAnimation(_firstItem, "start", true);
- // Handle changing scrollEnabled independent of user -> carousel interaction
- if (hasNewScrollEnabled) {
- this._setScrollEnabled(scrollEnabled);
- }
+ if (apparitionDelay) {
+ this._apparitionTimeout = setTimeout(() => {
+ apparitionCallback();
+ }, apparitionDelay);
+ } else {
+ apparitionCallback();
+ }
+ });
+ }
+
+ shouldComponentUpdate(nextProps, nextState) {
+ if (this.props.shouldOptimizeUpdates === false) {
+ return true;
+ } else {
+ return shallowCompare(this, nextProps, nextState);
+ }
+ }
+
+ componentDidUpdate(prevProps) {
+ const { interpolators } = this.state;
+ const {
+ firstItem,
+ itemHeight,
+ itemWidth,
+ scrollEnabled,
+ sliderHeight,
+ sliderWidth,
+ } = this.props;
+ const itemsLength = this._getCustomDataLength(this.props);
+
+ if (!itemsLength) {
+ return;
+ }
+
+ const nextFirstItem = this._getFirstItem(firstItem, this.props);
+ let nextActiveItem =
+ this._activeItem || this._activeItem === 0
+ ? this._activeItem
+ : nextFirstItem;
+
+ const hasNewSliderWidth =
+ sliderWidth && sliderWidth !== prevProps.sliderWidth;
+ const hasNewSliderHeight =
+ sliderHeight && sliderHeight !== prevProps.sliderHeight;
+ const hasNewItemWidth = itemWidth && itemWidth !== prevProps.itemWidth;
+ const hasNewItemHeight = itemHeight && itemHeight !== prevProps.itemHeight;
+ const hasNewScrollEnabled = scrollEnabled !== prevProps.scrollEnabled;
+
+ // Prevent issues with dynamically removed items
+ if (nextActiveItem > itemsLength - 1) {
+ nextActiveItem = itemsLength - 1;
+ }
+
+ // Handle changing scrollEnabled independent of user -> carousel interaction
+ if (hasNewScrollEnabled) {
+ this._setScrollEnabled(scrollEnabled);
+ }
+
+ if (
+ interpolators.length !== itemsLength ||
+ hasNewSliderWidth ||
+ hasNewSliderHeight ||
+ hasNewItemWidth ||
+ hasNewItemHeight
+ ) {
+ this._activeItem = nextActiveItem;
+ this._previousItemsLength = itemsLength;
+
+ this._initPositionsAndInterpolators(this.props);
+
+ // Handle scroll issue when dynamically removing items (see #133)
+ // This also fixes first item's active state on Android
+ // Because 'initialScrollIndex' apparently doesn't trigger scroll
+ if (this._previousItemsLength > itemsLength) {
+ this._hackActiveSlideAnimation(nextActiveItem, null, true);
+ }
- if (interpolators.length !== itemsLength || hasNewSliderWidth ||
- hasNewSliderHeight || hasNewItemWidth || hasNewItemHeight) {
- this._activeItem = nextActiveItem;
- this._previousItemsLength = itemsLength;
-
- this._initPositionsAndInterpolators(this.props);
-
- // Handle scroll issue when dynamically removing items (see #133)
- // This also fixes first item's active state on Android
- // Because 'initialScrollIndex' apparently doesn't trigger scroll
- if (this._previousItemsLength > itemsLength) {
- this._hackActiveSlideAnimation(nextActiveItem, null, true);
- }
-
- if (hasNewSliderWidth || hasNewSliderHeight || hasNewItemWidth || hasNewItemHeight) {
- this._snapToItem(nextActiveItem, false, false, false, false);
- }
- } else if (nextFirstItem !== this._previousFirstItem && nextFirstItem !== this._activeItem) {
- this._activeItem = nextFirstItem;
- this._previousFirstItem = nextFirstItem;
- this._snapToItem(nextFirstItem, false, true, false, false);
- }
+ if (
+ hasNewSliderWidth ||
+ hasNewSliderHeight ||
+ hasNewItemWidth ||
+ hasNewItemHeight
+ ) {
+ this._snapToItem(nextActiveItem, false, false, false, false);
+ }
+ } else if (
+ nextFirstItem !== this._previousFirstItem &&
+ nextFirstItem !== this._activeItem
+ ) {
+ this._activeItem = nextFirstItem;
+ this._previousFirstItem = nextFirstItem;
+ this._snapToItem(nextFirstItem, false, true, false, false);
+ }
+
+ if (this.props.onScroll !== prevProps.onScroll) {
+ this._setScrollHandler(this.props);
+ }
+ }
+
+ componentWillUnmount() {
+ this._mounted = false;
+ this.stopAutoplay();
+ clearTimeout(this._apparitionTimeout);
+ clearTimeout(this._hackSlideAnimationTimeout);
+ clearTimeout(this._enableAutoplayTimeout);
+ clearTimeout(this._autoplayTimeout);
+ clearTimeout(this._snapNoMomentumTimeout);
+ clearTimeout(this._edgeItemTimeout);
+ clearTimeout(this._lockScrollTimeout);
+ }
+
+ get realIndex() {
+ return this._activeItem;
+ }
+
+ get currentIndex() {
+ return this._getDataIndex(this._activeItem);
+ }
+
+ get currentScrollPosition() {
+ return this._currentContentOffset;
+ }
+
+ _setScrollHandler(props) {
+ // Native driver for scroll events
+ const scrollEventConfig = {
+ listener: this._onScroll,
+ useNativeDriver: true,
+ };
+ this._scrollPos = new Animated.Value(0);
+ const argMapping = props.vertical
+ ? [{ nativeEvent: { contentOffset: { y: this._scrollPos } } }]
+ : [{ nativeEvent: { contentOffset: { x: this._scrollPos } } }];
+
+ if (props.onScroll && Array.isArray(props.onScroll._argMapping)) {
+ // Because of a react-native issue https://github.com/facebook/react-native/issues/13294
+ argMapping.pop();
+ const [argMap] = props.onScroll._argMapping;
+ if (argMap && argMap.nativeEvent && argMap.nativeEvent.contentOffset) {
+ // Shares the same animated value passed in props
+ this._scrollPos =
+ argMap.nativeEvent.contentOffset.x ||
+ argMap.nativeEvent.contentOffset.y ||
+ this._scrollPos;
+ }
+ argMapping.push(...props.onScroll._argMapping);
+ }
+ this._onScrollHandler = Animated.event(argMapping, scrollEventConfig);
+ }
+
+ _needsScrollView() {
+ const { useScrollView } = this.props;
+ return (
+ useScrollView ||
+ !AnimatedFlatList ||
+ this._shouldUseStackLayout() ||
+ this._shouldUseTinderLayout()
+ );
+ }
+
+ _needsRTLAdaptations() {
+ const { vertical } = this.props;
+ return IS_RTL && !IS_IOS && !vertical;
+ }
+
+ _canLockScroll() {
+ const { scrollEnabled, enableMomentum, lockScrollWhileSnapping } =
+ this.props;
+ return scrollEnabled && !enableMomentum && lockScrollWhileSnapping;
+ }
+
+ _enableLoop() {
+ const { data, enableSnap, loop } = this.props;
+ return enableSnap && loop && data && data.length && data.length > 1;
+ }
+
+ _shouldAnimateSlides(props = this.props) {
+ const {
+ inactiveSlideOpacity,
+ inactiveSlideScale,
+ scrollInterpolator,
+ slideInterpolatedStyle,
+ } = props;
+ return (
+ inactiveSlideOpacity < 1 ||
+ inactiveSlideScale < 1 ||
+ !!scrollInterpolator ||
+ !!slideInterpolatedStyle ||
+ this._shouldUseShiftLayout() ||
+ this._shouldUseStackLayout() ||
+ this._shouldUseTinderLayout()
+ );
+ }
+
+ _shouldUseCustomAnimation() {
+ const { activeAnimationOptions } = this.props;
+ return (
+ !!activeAnimationOptions &&
+ !this._shouldUseStackLayout() &&
+ !this._shouldUseTinderLayout()
+ );
+ }
+
+ _shouldUseShiftLayout() {
+ const { inactiveSlideShift, layout } = this.props;
+ return layout === "default" && inactiveSlideShift !== 0;
+ }
+
+ _shouldUseStackLayout() {
+ return this.props.layout === "stack";
+ }
+
+ _shouldUseTinderLayout() {
+ return this.props.layout === "tinder";
+ }
+
+ _getCustomData(props = this.props) {
+ const { data, loopClonesPerSide } = props;
+ const dataLength = data && data.length;
+
+ if (!dataLength) {
+ return [];
+ }
+
+ if (!this._enableLoop()) {
+ return data;
+ }
+
+ let previousItems = [];
+ let nextItems = [];
+
+ if (loopClonesPerSide > dataLength) {
+ const dataMultiplier = Math.floor(loopClonesPerSide / dataLength);
+ const remainder = loopClonesPerSide % dataLength;
+
+ for (let i = 0; i < dataMultiplier; i++) {
+ previousItems.push(...data);
+ nextItems.push(...data);
+ }
- if (this.props.onScroll !== prevProps.onScroll) {
- this._setScrollHandler(this.props);
- }
+ previousItems.unshift(...data.slice(-remainder));
+ nextItems.push(...data.slice(0, remainder));
+ } else {
+ previousItems = data.slice(-loopClonesPerSide);
+ nextItems = data.slice(0, loopClonesPerSide);
}
- componentWillUnmount () {
- this._mounted = false;
- this.stopAutoplay();
- clearTimeout(this._apparitionTimeout);
- clearTimeout(this._hackSlideAnimationTimeout);
- clearTimeout(this._enableAutoplayTimeout);
- clearTimeout(this._autoplayTimeout);
- clearTimeout(this._snapNoMomentumTimeout);
- clearTimeout(this._edgeItemTimeout);
- clearTimeout(this._lockScrollTimeout);
- }
+ return previousItems.concat(data, nextItems);
+ }
- get realIndex () {
- return this._activeItem;
- }
+ _getCustomDataLength(props = this.props) {
+ const { data, loopClonesPerSide } = props;
+ const dataLength = data && data.length;
- get currentIndex () {
- return this._getDataIndex(this._activeItem);
+ if (!dataLength) {
+ return 0;
}
- get currentScrollPosition () {
- return this._currentContentOffset;
- }
+ return this._enableLoop() ? dataLength + 2 * loopClonesPerSide : dataLength;
+ }
- _setScrollHandler(props) {
- // Native driver for scroll events
- const scrollEventConfig = {
- listener: this._onScroll,
- useNativeDriver: true,
- };
- this._scrollPos = new Animated.Value(0);
- const argMapping = props.vertical
- ? [{ nativeEvent: { contentOffset: { y: this._scrollPos } } }]
- : [{ nativeEvent: { contentOffset: { x: this._scrollPos } } }];
-
- if (props.onScroll && Array.isArray(props.onScroll._argMapping)) {
- // Because of a react-native issue https://github.com/facebook/react-native/issues/13294
- argMapping.pop();
- const [ argMap ] = props.onScroll._argMapping;
- if (argMap && argMap.nativeEvent && argMap.nativeEvent.contentOffset) {
- // Shares the same animated value passed in props
- this._scrollPos =
- argMap.nativeEvent.contentOffset.x ||
- argMap.nativeEvent.contentOffset.y ||
- this._scrollPos;
- }
- argMapping.push(...props.onScroll._argMapping);
- }
- this._onScrollHandler = Animated.event(
- argMapping,
- scrollEventConfig
- );
- }
+ _getCustomIndex(index, props = this.props) {
+ const itemsLength = this._getCustomDataLength(props);
- _needsScrollView () {
- const { useScrollView } = this.props;
- return useScrollView || !AnimatedFlatList || this._shouldUseStackLayout() || this._shouldUseTinderLayout();
+ if (!itemsLength || (!index && index !== 0)) {
+ return 0;
}
- _needsRTLAdaptations () {
- const { vertical } = this.props;
- return IS_RTL && !IS_IOS && !vertical;
- }
+ return this._needsRTLAdaptations() ? itemsLength - index - 1 : index;
+ }
- _canLockScroll () {
- const { scrollEnabled, enableMomentum, lockScrollWhileSnapping } = this.props;
- return scrollEnabled && !enableMomentum && lockScrollWhileSnapping;
- }
+ _getDataIndex(index) {
+ const { data, loopClonesPerSide } = this.props;
+ const dataLength = data && data.length;
- _enableLoop () {
- const { data, enableSnap, loop } = this.props;
- return enableSnap && loop && data && data.length && data.length > 1;
+ if (!this._enableLoop() || !dataLength) {
+ return index;
}
- _shouldAnimateSlides (props = this.props) {
- const { inactiveSlideOpacity, inactiveSlideScale, scrollInterpolator, slideInterpolatedStyle } = props;
- return inactiveSlideOpacity < 1 ||
- inactiveSlideScale < 1 ||
- !!scrollInterpolator ||
- !!slideInterpolatedStyle ||
- this._shouldUseShiftLayout() ||
- this._shouldUseStackLayout() ||
- this._shouldUseTinderLayout();
- }
+ if (index >= dataLength + loopClonesPerSide) {
+ return loopClonesPerSide > dataLength
+ ? (index - loopClonesPerSide) % dataLength
+ : index - dataLength - loopClonesPerSide;
+ } else if (index < loopClonesPerSide) {
+ // TODO: is there a simpler way of determining the interpolated index?
+ if (loopClonesPerSide > dataLength) {
+ const baseDataIndexes = [];
+ const dataIndexes = [];
+ const dataMultiplier = Math.floor(loopClonesPerSide / dataLength);
+ const remainder = loopClonesPerSide % dataLength;
- _shouldUseCustomAnimation () {
- const { activeAnimationOptions } = this.props;
- return !!activeAnimationOptions && !this._shouldUseStackLayout() && !this._shouldUseTinderLayout();
- }
+ for (let i = 0; i < dataLength; i++) {
+ baseDataIndexes.push(i);
+ }
- _shouldUseShiftLayout () {
- const { inactiveSlideShift, layout } = this.props;
- return layout === 'default' && inactiveSlideShift !== 0;
- }
+ for (let j = 0; j < dataMultiplier; j++) {
+ dataIndexes.push(...baseDataIndexes);
+ }
- _shouldUseStackLayout () {
- return this.props.layout === 'stack';
+ dataIndexes.unshift(...baseDataIndexes.slice(-remainder));
+ return dataIndexes[index];
+ } else {
+ return index + dataLength - loopClonesPerSide;
+ }
+ } else {
+ return index - loopClonesPerSide;
+ }
+ }
+
+ // Used with `snapToItem()` and 'PaginationDot'
+ _getPositionIndex(index) {
+ const { loop, loopClonesPerSide } = this.props;
+ return loop ? index + loopClonesPerSide : index;
+ }
+
+ _getFirstItem(index, props = this.props) {
+ const { loopClonesPerSide } = props;
+ const itemsLength = this._getCustomDataLength(props);
+
+ if (!itemsLength || index > itemsLength - 1 || index < 0) {
+ return 0;
+ }
+
+ return this._enableLoop() ? index + loopClonesPerSide : index;
+ }
+
+ _getWrappedRef() {
+ if (
+ this._carouselRef &&
+ ((this._needsScrollView() && this._carouselRef.scrollTo) ||
+ (!this._needsScrollView() && this._carouselRef.scrollToOffset))
+ ) {
+ return this._carouselRef;
+ }
+ // https://github.com/facebook/react-native/issues/10635
+ // https://stackoverflow.com/a/48786374/8412141
+ return (
+ this._carouselRef &&
+ this._carouselRef.getNode &&
+ this._carouselRef.getNode()
+ );
+ }
+
+ _getScrollEnabled() {
+ return this._scrollEnabled;
+ }
+
+ _setScrollEnabled(scrollEnabled = true) {
+ const wrappedRef = this._getWrappedRef();
+
+ if (!wrappedRef || !wrappedRef.setNativeProps) {
+ return;
+ }
+
+ // 'setNativeProps()' is used instead of 'setState()' because the latter
+ // really takes a toll on Android behavior when momentum is disabled
+ wrappedRef.setNativeProps({ scrollEnabled });
+ this._scrollEnabled = scrollEnabled;
+ }
+
+ _getKeyExtractor(item, index) {
+ return this._needsScrollView()
+ ? `scrollview-item-${index}`
+ : `flatlist-item-${index}`;
+ }
+
+ _getScrollOffset(event) {
+ const { vertical } = this.props;
+ return (
+ (event &&
+ event.nativeEvent &&
+ event.nativeEvent.contentOffset &&
+ event.nativeEvent.contentOffset[vertical ? "y" : "x"]) ||
+ 0
+ );
+ }
+
+ _getContainerInnerMargin(opposite = false) {
+ const {
+ sliderWidth,
+ sliderHeight,
+ itemWidth,
+ itemHeight,
+ vertical,
+ activeSlideAlignment,
+ } = this.props;
+
+ if (
+ (activeSlideAlignment === "start" && !opposite) ||
+ (activeSlideAlignment === "end" && opposite)
+ ) {
+ return 0;
+ } else if (
+ (activeSlideAlignment === "end" && !opposite) ||
+ (activeSlideAlignment === "start" && opposite)
+ ) {
+ return vertical ? sliderHeight - itemHeight : sliderWidth - itemWidth;
+ } else {
+ return vertical
+ ? (sliderHeight - itemHeight) / 2
+ : (sliderWidth - itemWidth) / 2;
+ }
+ }
+
+ _getViewportOffset() {
+ const {
+ sliderWidth,
+ sliderHeight,
+ itemWidth,
+ itemHeight,
+ vertical,
+ activeSlideAlignment,
+ } = this.props;
+
+ if (activeSlideAlignment === "start") {
+ return vertical ? itemHeight / 2 : itemWidth / 2;
+ } else if (activeSlideAlignment === "end") {
+ return vertical
+ ? sliderHeight - itemHeight / 2
+ : sliderWidth - itemWidth / 2;
+ } else {
+ return vertical ? sliderHeight / 2 : sliderWidth / 2;
+ }
+ }
+
+ _getCenter(offset) {
+ return offset + this._getViewportOffset() - this._getContainerInnerMargin();
+ }
+
+ _getActiveItem(offset) {
+ const { activeSlideOffset, swipeThreshold } = this.props;
+ const center = this._getCenter(offset);
+ const centerOffset = activeSlideOffset || swipeThreshold;
+
+ for (let i = 0; i < this._positions.length; i++) {
+ const { start, end } = this._positions[i];
+ if (center + centerOffset >= start && center - centerOffset <= end) {
+ return i;
+ }
}
- _shouldUseTinderLayout () {
- return this.props.layout === 'tinder';
+ const lastIndex = this._positions.length - 1;
+ if (
+ this._positions[lastIndex] &&
+ center - centerOffset > this._positions[lastIndex].end
+ ) {
+ return lastIndex;
}
- _getCustomData (props = this.props) {
- const { data, loopClonesPerSide } = props;
- const dataLength = data && data.length;
+ return 0;
+ }
- if (!dataLength) {
- return [];
- }
+ _initPositionsAndInterpolators(props = this.props) {
+ const { data, itemWidth, itemHeight, scrollInterpolator, vertical } = props;
+ const sizeRef = vertical ? itemHeight : itemWidth;
- if (!this._enableLoop()) {
- return data;
- }
+ if (!data || !data.length) {
+ return;
+ }
- let previousItems = [];
- let nextItems = [];
+ let interpolators = [];
+ this._positions = [];
- if (loopClonesPerSide > dataLength) {
- const dataMultiplier = Math.floor(loopClonesPerSide / dataLength);
- const remainder = loopClonesPerSide % dataLength;
+ this._getCustomData(props).forEach((itemData, index) => {
+ const _index = this._getCustomIndex(index, props);
+ let animatedValue;
- for (let i = 0; i < dataMultiplier; i++) {
- previousItems.push(...data);
- nextItems.push(...data);
- }
+ this._positions[index] = {
+ start: index * sizeRef,
+ end: index * sizeRef + sizeRef,
+ };
- previousItems.unshift(...data.slice(-remainder));
- nextItems.push(...data.slice(0, remainder));
- } else {
- previousItems = data.slice(-loopClonesPerSide);
- nextItems = data.slice(0, loopClonesPerSide);
+ if (!this._shouldAnimateSlides(props)) {
+ animatedValue = new Animated.Value(1);
+ } else if (this._shouldUseCustomAnimation()) {
+ animatedValue = new Animated.Value(_index === this._activeItem ? 1 : 0);
+ } else {
+ let interpolator;
+
+ if (scrollInterpolator) {
+ interpolator = scrollInterpolator(_index, props);
+ } else if (this._shouldUseStackLayout()) {
+ interpolator = stackScrollInterpolator(_index, props);
+ } else if (this._shouldUseTinderLayout()) {
+ interpolator = tinderScrollInterpolator(_index, props);
}
- return previousItems.concat(data, nextItems);
- }
+ if (
+ !interpolator ||
+ !interpolator.inputRange ||
+ !interpolator.outputRange
+ ) {
+ interpolator = defaultScrollInterpolator(_index, props);
+ }
- _getCustomDataLength (props = this.props) {
- const { data, loopClonesPerSide } = props;
- const dataLength = data && data.length;
+ animatedValue = this._scrollPos.interpolate({
+ ...interpolator,
+ extrapolate: "clamp",
+ });
+ }
- if (!dataLength) {
- return 0;
- }
+ interpolators.push(animatedValue);
+ });
- return this._enableLoop() ? dataLength + (2 * loopClonesPerSide) : dataLength;
- }
+ this.setState({ interpolators });
+ }
- _getCustomIndex (index, props = this.props) {
- const itemsLength = this._getCustomDataLength(props);
+ _getSlideAnimation(index, toValue) {
+ const { interpolators } = this.state;
+ const { activeAnimationType, activeAnimationOptions } = this.props;
- if (!itemsLength || (!index && index !== 0)) {
- return 0;
- }
+ const animatedValue = interpolators && interpolators[index];
- return this._needsRTLAdaptations() ? itemsLength - index - 1 : index;
+ if (!animatedValue && animatedValue !== 0) {
+ return null;
}
- _getDataIndex (index) {
- const { data, loopClonesPerSide } = this.props;
- const dataLength = data && data.length;
-
- if (!this._enableLoop() || !dataLength) {
- return index;
- }
+ const animationCommonOptions = {
+ isInteraction: false,
+ useNativeDriver: true,
+ ...activeAnimationOptions,
+ toValue: toValue,
+ };
- if (index >= dataLength + loopClonesPerSide) {
- return loopClonesPerSide > dataLength ?
- (index - loopClonesPerSide) % dataLength :
- index - dataLength - loopClonesPerSide;
- } else if (index < loopClonesPerSide) {
- // TODO: is there a simpler way of determining the interpolated index?
- if (loopClonesPerSide > dataLength) {
- const baseDataIndexes = [];
- const dataIndexes = [];
- const dataMultiplier = Math.floor(loopClonesPerSide / dataLength);
- const remainder = loopClonesPerSide % dataLength;
-
- for (let i = 0; i < dataLength; i++) {
- baseDataIndexes.push(i);
- }
-
- for (let j = 0; j < dataMultiplier; j++) {
- dataIndexes.push(...baseDataIndexes);
- }
-
- dataIndexes.unshift(...baseDataIndexes.slice(-remainder));
- return dataIndexes[index];
- } else {
- return index + dataLength - loopClonesPerSide;
- }
- } else {
- return index - loopClonesPerSide;
+ return Animated.parallel([
+ Animated["timing"](animatedValue, {
+ ...animationCommonOptions,
+ easing: Easing.linear,
+ }),
+ Animated[activeAnimationType](animatedValue, {
+ ...animationCommonOptions,
+ }),
+ ]);
+ }
+
+ _playCustomSlideAnimation(current, next) {
+ const { interpolators } = this.state;
+ const itemsLength = this._getCustomDataLength();
+ const _currentIndex = this._getCustomIndex(current);
+ const _currentDataIndex = this._getDataIndex(_currentIndex);
+ const _nextIndex = this._getCustomIndex(next);
+ const _nextDataIndex = this._getDataIndex(_nextIndex);
+ let animations = [];
+
+ // Keep animations in sync when looping
+ if (this._enableLoop()) {
+ for (let i = 0; i < itemsLength; i++) {
+ if (this._getDataIndex(i) === _currentDataIndex && interpolators[i]) {
+ animations.push(this._getSlideAnimation(i, 0));
+ } else if (
+ this._getDataIndex(i) === _nextDataIndex &&
+ interpolators[i]
+ ) {
+ animations.push(this._getSlideAnimation(i, 1));
}
+ }
+ } else {
+ if (interpolators[current]) {
+ animations.push(this._getSlideAnimation(current, 0));
+ }
+ if (interpolators[next]) {
+ animations.push(this._getSlideAnimation(next, 1));
+ }
}
- // Used with `snapToItem()` and 'PaginationDot'
- _getPositionIndex (index) {
- const { loop, loopClonesPerSide } = this.props;
- return loop ? index + loopClonesPerSide : index;
- }
-
- _getFirstItem (index, props = this.props) {
- const { loopClonesPerSide } = props;
- const itemsLength = this._getCustomDataLength(props);
+ Animated.parallel(animations, { stopTogether: false }).start();
+ }
- if (!itemsLength || index > itemsLength - 1 || index < 0) {
- return 0;
- }
+ _hackActiveSlideAnimation(index, goTo, force = false) {
+ const { data } = this.props;
- return this._enableLoop() ? index + loopClonesPerSide : index;
+ if (
+ !this._mounted ||
+ !this._carouselRef ||
+ !this._positions[index] ||
+ (!force && this._enableLoop())
+ ) {
+ return;
}
- _getWrappedRef () {
- if (this._carouselRef && (
- (this._needsScrollView() && this._carouselRef.scrollTo) ||
- (!this._needsScrollView() && this._carouselRef.scrollToOffset)
- )) {
- return this._carouselRef;
- }
- // https://github.com/facebook/react-native/issues/10635
- // https://stackoverflow.com/a/48786374/8412141
- return this._carouselRef && this._carouselRef.getNode && this._carouselRef.getNode();
- }
+ const offset = this._positions[index] && this._positions[index].start;
- _getScrollEnabled () {
- return this._scrollEnabled;
+ if (!offset && offset !== 0) {
+ return;
}
- _setScrollEnabled (scrollEnabled = true) {
- const wrappedRef = this._getWrappedRef();
+ const itemsLength = data && data.length;
+ const direction = goTo || itemsLength === 1 ? "start" : "end";
- if (!wrappedRef || !wrappedRef.setNativeProps) {
- return;
- }
+ this._scrollTo(offset + (direction === "start" ? -1 : 1), false);
- // 'setNativeProps()' is used instead of 'setState()' because the latter
- // really takes a toll on Android behavior when momentum is disabled
- wrappedRef.setNativeProps({ scrollEnabled });
- this._scrollEnabled = scrollEnabled;
- }
+ clearTimeout(this._hackSlideAnimationTimeout);
+ this._hackSlideAnimationTimeout = setTimeout(() => {
+ this._scrollTo(offset, false);
+ }, 50); // works randomly when set to '0'
+ }
- _getKeyExtractor (item, index) {
- return this._needsScrollView() ? `scrollview-item-${index}` : `flatlist-item-${index}`;
- }
+ _lockScroll() {
+ const { lockScrollTimeoutDuration } = this.props;
+ clearTimeout(this._lockScrollTimeout);
+ this._lockScrollTimeout = setTimeout(() => {
+ this._releaseScroll();
+ }, lockScrollTimeoutDuration);
+ this._setScrollEnabled(false);
+ }
- _getScrollOffset (event) {
- const { vertical } = this.props;
- return (event && event.nativeEvent && event.nativeEvent.contentOffset &&
- event.nativeEvent.contentOffset[vertical ? 'y' : 'x']) || 0;
- }
+ _releaseScroll() {
+ clearTimeout(this._lockScrollTimeout);
+ this._setScrollEnabled(true);
+ }
- _getContainerInnerMargin (opposite = false) {
- const { sliderWidth, sliderHeight, itemWidth, itemHeight, vertical, activeSlideAlignment } = this.props;
+ _repositionScroll(index) {
+ const { data, loopClonesPerSide } = this.props;
+ const dataLength = data && data.length;
- if ((activeSlideAlignment === 'start' && !opposite) ||
- (activeSlideAlignment === 'end' && opposite)) {
- return 0;
- } else if ((activeSlideAlignment === 'end' && !opposite) ||
- (activeSlideAlignment === 'start' && opposite)) {
- return vertical ? sliderHeight - itemHeight : sliderWidth - itemWidth;
- } else {
- return vertical ? (sliderHeight - itemHeight) / 2 : (sliderWidth - itemWidth) / 2;
- }
+ if (
+ !this._enableLoop() ||
+ !dataLength ||
+ (index >= loopClonesPerSide && index < dataLength + loopClonesPerSide)
+ ) {
+ return;
}
- _getViewportOffset () {
- const { sliderWidth, sliderHeight, itemWidth, itemHeight, vertical, activeSlideAlignment } = this.props;
+ let repositionTo = index;
- if (activeSlideAlignment === 'start') {
- return vertical ? itemHeight / 2 : itemWidth / 2;
- } else if (activeSlideAlignment === 'end') {
- return vertical ?
- sliderHeight - (itemHeight / 2) :
- sliderWidth - (itemWidth / 2);
- } else {
- return vertical ? sliderHeight / 2 : sliderWidth / 2;
- }
+ if (index >= dataLength + loopClonesPerSide) {
+ repositionTo = index - dataLength;
+ } else if (index < loopClonesPerSide) {
+ repositionTo = index + dataLength;
}
- _getCenter (offset) {
- return offset + this._getViewportOffset() - this._getContainerInnerMargin();
- }
+ this._snapToItem(repositionTo, false, false, false, false);
+ }
- _getActiveItem (offset) {
- const { activeSlideOffset, swipeThreshold } = this.props;
- const center = this._getCenter(offset);
- const centerOffset = activeSlideOffset || swipeThreshold;
+ _scrollTo(offset, animated = true) {
+ const { vertical } = this.props;
+ const wrappedRef = this._getWrappedRef();
- for (let i = 0; i < this._positions.length; i++) {
- const { start, end } = this._positions[i];
- if (center + centerOffset >= start && center - centerOffset <= end) {
- return i;
- }
- }
+ if (!this._mounted || !wrappedRef) {
+ return;
+ }
- const lastIndex = this._positions.length - 1;
- if (this._positions[lastIndex] && center - centerOffset > this._positions[lastIndex].end) {
- return lastIndex;
+ const specificOptions = this._needsScrollView()
+ ? {
+ x: vertical ? 0 : offset,
+ y: vertical ? offset : 0,
}
+ : {
+ offset,
+ };
+ const options = {
+ ...specificOptions,
+ animated,
+ };
- return 0;
+ if (this._needsScrollView()) {
+ wrappedRef.scrollTo(options);
+ } else {
+ wrappedRef.scrollToOffset(options);
}
+ }
- _initPositionsAndInterpolators (props = this.props) {
- const { data, itemWidth, itemHeight, scrollInterpolator, vertical } = props;
- const sizeRef = vertical ? itemHeight : itemWidth;
+ _onScroll(event) {
+ const { callbackOffsetMargin, enableMomentum, onScroll } = this.props;
- if (!data || !data.length) {
- return;
- }
+ const scrollOffset = event
+ ? this._getScrollOffset(event)
+ : this._currentContentOffset;
+ const nextActiveItem = this._getActiveItem(scrollOffset);
+ const itemReached = nextActiveItem === this._itemToSnapTo;
+ const scrollConditions =
+ scrollOffset >= this._scrollOffsetRef - callbackOffsetMargin &&
+ scrollOffset <= this._scrollOffsetRef + callbackOffsetMargin;
- let interpolators = [];
- this._positions = [];
-
- this._getCustomData(props).forEach((itemData, index) => {
- const _index = this._getCustomIndex(index, props);
- let animatedValue;
-
- this._positions[index] = {
- start: index * sizeRef,
- end: index * sizeRef + sizeRef
- };
-
- if (!this._shouldAnimateSlides(props)) {
- animatedValue = new Animated.Value(1);
- } else if (this._shouldUseCustomAnimation()) {
- animatedValue = new Animated.Value(_index === this._activeItem ? 1 : 0);
- } else {
- let interpolator;
-
- if (scrollInterpolator) {
- interpolator = scrollInterpolator(_index, props);
- } else if (this._shouldUseStackLayout()) {
- interpolator = stackScrollInterpolator(_index, props);
- } else if (this._shouldUseTinderLayout()) {
- interpolator = tinderScrollInterpolator(_index, props);
- }
-
- if (!interpolator || !interpolator.inputRange || !interpolator.outputRange) {
- interpolator = defaultScrollInterpolator(_index, props);
- }
-
- animatedValue = this._scrollPos.interpolate({
- ...interpolator,
- extrapolate: 'clamp'
- });
- }
-
- interpolators.push(animatedValue);
- });
+ this._currentContentOffset = scrollOffset;
+ this._onScrollTriggered = true;
+ this._lastScrollDate = Date.now();
- this.setState({ interpolators });
+ if (
+ this._activeItem !== nextActiveItem &&
+ this._shouldUseCustomAnimation()
+ ) {
+ this._playCustomSlideAnimation(this._activeItem, nextActiveItem);
}
- _getSlideAnimation (index, toValue) {
- const { interpolators } = this.state;
- const { activeAnimationType, activeAnimationOptions } = this.props;
+ if (enableMomentum) {
+ clearTimeout(this._snapNoMomentumTimeout);
- const animatedValue = interpolators && interpolators[index];
+ if (this._activeItem !== nextActiveItem) {
+ this._activeItem = nextActiveItem;
+ }
- if (!animatedValue && animatedValue !== 0) {
- return null;
+ if (itemReached) {
+ if (this._canFireBeforeCallback) {
+ this._onBeforeSnap(this._getDataIndex(nextActiveItem));
}
- const animationCommonOptions = {
- isInteraction: false,
- useNativeDriver: true,
- ...activeAnimationOptions,
- toValue: toValue
- };
-
- return Animated.parallel([
- Animated['timing'](
- animatedValue,
- { ...animationCommonOptions, easing: Easing.linear }
- ),
- Animated[activeAnimationType](
- animatedValue,
- { ...animationCommonOptions }
- )
- ]);
- }
-
- _playCustomSlideAnimation (current, next) {
- const { interpolators } = this.state;
- const itemsLength = this._getCustomDataLength();
- const _currentIndex = this._getCustomIndex(current);
- const _currentDataIndex = this._getDataIndex(_currentIndex);
- const _nextIndex = this._getCustomIndex(next);
- const _nextDataIndex = this._getDataIndex(_nextIndex);
- let animations = [];
-
- // Keep animations in sync when looping
- if (this._enableLoop()) {
- for (let i = 0; i < itemsLength; i++) {
- if (this._getDataIndex(i) === _currentDataIndex && interpolators[i]) {
- animations.push(this._getSlideAnimation(i, 0));
- } else if (this._getDataIndex(i) === _nextDataIndex && interpolators[i]) {
- animations.push(this._getSlideAnimation(i, 1));
- }
- }
- } else {
- if (interpolators[current]) {
- animations.push(this._getSlideAnimation(current, 0));
- }
- if (interpolators[next]) {
- animations.push(this._getSlideAnimation(next, 1));
- }
+ if (scrollConditions && this._canFireCallback) {
+ this._onSnap(this._getDataIndex(nextActiveItem));
}
+ }
+ } else if (this._activeItem !== nextActiveItem && itemReached) {
+ if (this._canFireBeforeCallback) {
+ this._onBeforeSnap(this._getDataIndex(nextActiveItem));
+ }
- Animated.parallel(animations, { stopTogether: false }).start();
- }
-
- _hackActiveSlideAnimation (index, goTo, force = false) {
- const { data } = this.props;
+ if (scrollConditions) {
+ this._activeItem = nextActiveItem;
- if (!this._mounted || !this._carouselRef || !this._positions[index] || (!force && this._enableLoop())) {
- return;
+ if (this._canLockScroll()) {
+ this._releaseScroll();
}
- const offset = this._positions[index] && this._positions[index].start;
-
- if (!offset && offset !== 0) {
- return;
+ if (this._canFireCallback) {
+ this._onSnap(this._getDataIndex(nextActiveItem));
}
-
- const itemsLength = data && data.length;
- const direction = goTo || itemsLength === 1 ? 'start' : 'end';
-
- this._scrollTo(offset + (direction === 'start' ? -1 : 1), false);
-
- clearTimeout(this._hackSlideAnimationTimeout);
- this._hackSlideAnimationTimeout = setTimeout(() => {
- this._scrollTo(offset, false);
- }, 50); // works randomly when set to '0'
+ }
}
- _lockScroll () {
- const { lockScrollTimeoutDuration } = this.props;
- clearTimeout(this._lockScrollTimeout);
- this._lockScrollTimeout = setTimeout(() => {
- this._releaseScroll();
- }, lockScrollTimeoutDuration);
- this._setScrollEnabled(false);
+ if (
+ nextActiveItem === this._itemToSnapTo &&
+ scrollOffset === this._scrollOffsetRef
+ ) {
+ this._repositionScroll(nextActiveItem);
}
- _releaseScroll () {
- clearTimeout(this._lockScrollTimeout);
- this._setScrollEnabled(true);
+ if (typeof onScroll === "function" && event) {
+ onScroll(event);
}
+ }
- _repositionScroll (index) {
- const { data, loopClonesPerSide } = this.props;
- const dataLength = data && data.length;
-
- if (!this._enableLoop() || !dataLength ||
- (index >= loopClonesPerSide && index < dataLength + loopClonesPerSide)) {
- return;
- }
-
- let repositionTo = index;
-
- if (index >= dataLength + loopClonesPerSide) {
- repositionTo = index - dataLength;
- } else if (index < loopClonesPerSide) {
- repositionTo = index + dataLength;
- }
+ _onStartShouldSetResponderCapture(event) {
+ const { onStartShouldSetResponderCapture } = this.props;
- this._snapToItem(repositionTo, false, false, false, false);
+ if (onStartShouldSetResponderCapture) {
+ onStartShouldSetResponderCapture(event);
}
- _scrollTo (offset, animated = true) {
- const { vertical } = this.props;
- const wrappedRef = this._getWrappedRef();
+ return this._getScrollEnabled();
+ }
- if (!this._mounted || !wrappedRef) {
- return;
- }
-
- const specificOptions = this._needsScrollView() ? {
- x: vertical ? 0 : offset,
- y: vertical ? offset : 0
- } : {
- offset
- };
- const options = {
- ...specificOptions,
- animated
- };
+ _onTouchStart() {
+ const { onTouchStart } = this.props;
- if (this._needsScrollView()) {
- wrappedRef.scrollTo(options);
- } else {
- wrappedRef.scrollToOffset(options);
- }
+ // `onTouchStart` is fired even when `scrollEnabled` is set to `false`
+ if (this._getScrollEnabled() !== false && this._autoplaying) {
+ this.pauseAutoPlay();
}
- _onScroll (event) {
- const { callbackOffsetMargin, enableMomentum, onScroll } = this.props;
-
- const scrollOffset = event ? this._getScrollOffset(event) : this._currentContentOffset;
- const nextActiveItem = this._getActiveItem(scrollOffset);
- const itemReached = nextActiveItem === this._itemToSnapTo;
- const scrollConditions =
- scrollOffset >= this._scrollOffsetRef - callbackOffsetMargin &&
- scrollOffset <= this._scrollOffsetRef + callbackOffsetMargin;
-
- this._currentContentOffset = scrollOffset;
- this._onScrollTriggered = true;
- this._lastScrollDate = Date.now();
-
- if (this._activeItem !== nextActiveItem && this._shouldUseCustomAnimation()) {
- this._playCustomSlideAnimation(this._activeItem, nextActiveItem);
- }
-
- if (enableMomentum) {
- clearTimeout(this._snapNoMomentumTimeout);
-
- if (this._activeItem !== nextActiveItem) {
- this._activeItem = nextActiveItem;
- }
-
- if (itemReached) {
- if (this._canFireBeforeCallback) {
- this._onBeforeSnap(this._getDataIndex(nextActiveItem));
- }
-
- if (scrollConditions && this._canFireCallback) {
- this._onSnap(this._getDataIndex(nextActiveItem));
- }
- }
- } else if (this._activeItem !== nextActiveItem && itemReached) {
- if (this._canFireBeforeCallback) {
- this._onBeforeSnap(this._getDataIndex(nextActiveItem));
- }
-
- if (scrollConditions) {
- this._activeItem = nextActiveItem;
-
- if (this._canLockScroll()) {
- this._releaseScroll();
- }
-
- if (this._canFireCallback) {
- this._onSnap(this._getDataIndex(nextActiveItem));
- }
- }
- }
+ if (onTouchStart) {
+ onTouchStart();
+ }
+ }
- if (nextActiveItem === this._itemToSnapTo &&
- scrollOffset === this._scrollOffsetRef) {
- this._repositionScroll(nextActiveItem);
- }
+ _onTouchEnd() {
+ const { onTouchEnd } = this.props;
- if (typeof onScroll === "function" && event) {
- onScroll(event);
- }
+ if (
+ this._getScrollEnabled() !== false &&
+ this._autoplay &&
+ !this._autoplaying
+ ) {
+ // This event is buggy on Android, so a fallback is provided in _onScrollEnd()
+ this.startAutoplay();
}
- _onStartShouldSetResponderCapture (event) {
- const { onStartShouldSetResponderCapture } = this.props;
+ if (onTouchEnd) {
+ onTouchEnd();
+ }
+ }
- if (onStartShouldSetResponderCapture) {
- onStartShouldSetResponderCapture(event);
- }
+ // Used when `enableSnap` is ENABLED
+ _onScrollBeginDrag(event) {
+ const { onScrollBeginDrag } = this.props;
- return this._getScrollEnabled();
+ if (!this._getScrollEnabled()) {
+ return;
}
- _onTouchStart () {
- const { onTouchStart } = this.props
+ this._scrollStartOffset = this._getScrollOffset(event);
+ this._scrollStartActive = this._getActiveItem(this._scrollStartOffset);
+ this._ignoreNextMomentum = false;
+ // this._canFireCallback = false;
- // `onTouchStart` is fired even when `scrollEnabled` is set to `false`
- if (this._getScrollEnabled() !== false && this._autoplaying) {
- this.pauseAutoPlay();
- }
-
- if (onTouchStart) {
- onTouchStart()
- }
+ if (onScrollBeginDrag) {
+ onScrollBeginDrag(event);
}
+ }
- _onTouchEnd () {
- const { onTouchEnd } = this.props
+ // Used when `enableMomentum` is DISABLED
+ _onScrollEndDrag(event) {
+ const { onScrollEndDrag } = this.props;
- if (this._getScrollEnabled() !== false && this._autoplay && !this._autoplaying) {
- // This event is buggy on Android, so a fallback is provided in _onScrollEnd()
- this.startAutoplay();
- }
-
- if (onTouchEnd) {
- onTouchEnd()
- }
+ if (this._carouselRef) {
+ this._onScrollEnd && this._onScrollEnd();
}
- // Used when `enableSnap` is ENABLED
- _onScrollBeginDrag (event) {
- const { onScrollBeginDrag } = this.props;
+ if (onScrollEndDrag) {
+ onScrollEndDrag(event);
+ }
+ }
- if (!this._getScrollEnabled()) {
- return;
- }
+ // Used when `enableMomentum` is ENABLED
+ _onMomentumScrollEnd(event) {
+ const { onMomentumScrollEnd } = this.props;
- this._scrollStartOffset = this._getScrollOffset(event);
- this._scrollStartActive = this._getActiveItem(this._scrollStartOffset);
- this._ignoreNextMomentum = false;
- // this._canFireCallback = false;
+ if (this._carouselRef) {
+ this._onScrollEnd && this._onScrollEnd();
+ }
- if (onScrollBeginDrag) {
- onScrollBeginDrag(event);
- }
+ if (onMomentumScrollEnd) {
+ onMomentumScrollEnd(event);
}
+ }
- // Used when `enableMomentum` is DISABLED
- _onScrollEndDrag (event) {
- const { onScrollEndDrag } = this.props;
+ _onScrollEnd(event) {
+ const { autoplayDelay, enableSnap } = this.props;
- if (this._carouselRef) {
- this._onScrollEnd && this._onScrollEnd();
- }
+ if (this._ignoreNextMomentum) {
+ // iOS fix
+ this._ignoreNextMomentum = false;
+ return;
+ }
- if (onScrollEndDrag) {
- onScrollEndDrag(event);
- }
+ if (this._currentContentOffset === this._scrollEndOffset) {
+ return;
}
- // Used when `enableMomentum` is ENABLED
- _onMomentumScrollEnd (event) {
- const { onMomentumScrollEnd } = this.props;
+ this._scrollEndOffset = this._currentContentOffset;
+ this._scrollEndActive = this._getActiveItem(this._scrollEndOffset);
- if (this._carouselRef) {
- this._onScrollEnd && this._onScrollEnd();
- }
-
- if (onMomentumScrollEnd) {
- onMomentumScrollEnd(event);
- }
+ if (enableSnap) {
+ this._snapScroll(this._scrollEndOffset - this._scrollStartOffset);
}
- _onScrollEnd (event) {
- const { autoplayDelay, enableSnap } = this.props;
+ // The touchEnd event is buggy on Android, so this will serve as a fallback whenever needed
+ // https://github.com/facebook/react-native/issues/9439
+ if (this._autoplay && !this._autoplaying) {
+ clearTimeout(this._enableAutoplayTimeout);
+ this._enableAutoplayTimeout = setTimeout(() => {
+ this.startAutoplay();
+ }, autoplayDelay + 50);
+ }
+ }
- if (this._ignoreNextMomentum) {
- // iOS fix
- this._ignoreNextMomentum = false;
- return;
- }
+ // Due to a bug, this event is only fired on iOS
+ // https://github.com/facebook/react-native/issues/6791
+ // it's fine since we're only fixing an iOS bug in it, so ...
+ _onTouchRelease(event) {
+ const { enableMomentum } = this.props;
- if (this._currentContentOffset === this._scrollEndOffset) {
- return;
- }
+ if (enableMomentum && IS_IOS) {
+ clearTimeout(this._snapNoMomentumTimeout);
+ this._snapNoMomentumTimeout = setTimeout(() => {
+ this._snapToItem(this._activeItem);
+ }, 100);
+ }
+ }
- this._scrollEndOffset = this._currentContentOffset;
- this._scrollEndActive = this._getActiveItem(this._scrollEndOffset);
+ _onLayout(event) {
+ const { onLayout } = this.props;
- if (enableSnap) {
- this._snapScroll(this._scrollEndOffset - this._scrollStartOffset);
- }
+ // Prevent unneeded actions during the first 'onLayout' (triggered on init)
+ if (this._onLayoutInitDone) {
+ this._initPositionsAndInterpolators();
+ this._snapToItem(this._activeItem, false, false, false, false);
+ } else {
+ this._onLayoutInitDone = true;
+ }
- // The touchEnd event is buggy on Android, so this will serve as a fallback whenever needed
- // https://github.com/facebook/react-native/issues/9439
- if (this._autoplay && !this._autoplaying) {
- clearTimeout(this._enableAutoplayTimeout);
- this._enableAutoplayTimeout = setTimeout(() => {
- this.startAutoplay();
- }, autoplayDelay + 50);
- }
+ if (onLayout) {
+ onLayout(event);
}
+ }
- // Due to a bug, this event is only fired on iOS
- // https://github.com/facebook/react-native/issues/6791
- // it's fine since we're only fixing an iOS bug in it, so ...
- _onTouchRelease (event) {
- const { enableMomentum } = this.props;
+ _snapScroll(delta) {
+ const { swipeThreshold } = this.props;
- if (enableMomentum && IS_IOS) {
- clearTimeout(this._snapNoMomentumTimeout);
- this._snapNoMomentumTimeout = setTimeout(() => {
- this._snapToItem(this._activeItem);
- }, 100);
- }
+ // When using momentum and releasing the touch with
+ // no velocity, scrollEndActive will be undefined (iOS)
+ if (!this._scrollEndActive && this._scrollEndActive !== 0 && IS_IOS) {
+ this._scrollEndActive = this._scrollStartActive;
}
- _onLayout (event) {
- const { onLayout } = this.props;
-
- // Prevent unneeded actions during the first 'onLayout' (triggered on init)
- if (this._onLayoutInitDone) {
- this._initPositionsAndInterpolators();
- this._snapToItem(this._activeItem, false, false, false, false);
+ if (this._scrollStartActive !== this._scrollEndActive) {
+ // Snap to the new active item
+ this._snapToItem(this._scrollEndActive);
+ } else {
+ // Snap depending on delta
+ if (delta > 0) {
+ if (delta > swipeThreshold) {
+ this._snapToItem(this._scrollStartActive + 1);
} else {
- this._onLayoutInitDone = true;
+ this._snapToItem(this._scrollEndActive);
}
-
- if (onLayout) {
- onLayout(event);
+ } else if (delta < 0) {
+ if (delta < -swipeThreshold) {
+ this._snapToItem(this._scrollStartActive - 1);
+ } else {
+ this._snapToItem(this._scrollEndActive);
}
+ } else {
+ // Snap to current
+ this._snapToItem(this._scrollEndActive);
+ }
}
+ }
- _snapScroll (delta) {
- const { swipeThreshold } = this.props;
+ _snapToItem(
+ index,
+ animated = true,
+ fireCallback = true,
+ initial = false,
+ lockScroll = true
+ ) {
+ const { enableMomentum, onSnapToItem, onBeforeSnapToItem } = this.props;
+ const itemsLength = this._getCustomDataLength();
+ const wrappedRef = this._getWrappedRef();
- // When using momentum and releasing the touch with
- // no velocity, scrollEndActive will be undefined (iOS)
- if (!this._scrollEndActive && this._scrollEndActive !== 0 && IS_IOS) {
- this._scrollEndActive = this._scrollStartActive;
- }
+ if (!itemsLength || !wrappedRef) {
+ return;
+ }
- if (this._scrollStartActive !== this._scrollEndActive) {
- // Snap to the new active item
- this._snapToItem(this._scrollEndActive);
- } else {
- // Snap depending on delta
- if (delta > 0) {
- if (delta > swipeThreshold) {
- this._snapToItem(this._scrollStartActive + 1);
- } else {
- this._snapToItem(this._scrollEndActive);
- }
- } else if (delta < 0) {
- if (delta < -swipeThreshold) {
- this._snapToItem(this._scrollStartActive - 1);
- } else {
- this._snapToItem(this._scrollEndActive);
- }
- } else {
- // Snap to current
- this._snapToItem(this._scrollEndActive);
- }
- }
+ if (!index || index < 0) {
+ index = 0;
+ } else if (itemsLength > 0 && index >= itemsLength) {
+ index = itemsLength - 1;
}
- _snapToItem (index, animated = true, fireCallback = true, initial = false, lockScroll = true) {
- const { enableMomentum, onSnapToItem, onBeforeSnapToItem } = this.props;
- const itemsLength = this._getCustomDataLength();
- const wrappedRef = this._getWrappedRef();
+ if (index !== this._previousActiveItem) {
+ this._previousActiveItem = index;
- if (!itemsLength || !wrappedRef) {
- return;
- }
+ // Placed here to allow overscrolling for edges items
+ if (lockScroll && this._canLockScroll()) {
+ this._lockScroll();
+ }
- if (!index || index < 0) {
- index = 0;
- } else if (itemsLength > 0 && index >= itemsLength) {
- index = itemsLength - 1;
+ if (fireCallback) {
+ if (onBeforeSnapToItem) {
+ this._canFireBeforeCallback = true;
}
- if (index !== this._previousActiveItem) {
- this._previousActiveItem = index;
-
- // Placed here to allow overscrolling for edges items
- if (lockScroll && this._canLockScroll()) {
- this._lockScroll();
- }
-
- if (fireCallback) {
- if (onBeforeSnapToItem) {
- this._canFireBeforeCallback = true;
- }
-
- if (onSnapToItem) {
- this._canFireCallback = true;
- }
- }
+ if (onSnapToItem) {
+ this._canFireCallback = true;
}
+ }
+ }
- this._itemToSnapTo = index;
- this._scrollOffsetRef = this._positions[index] && this._positions[index].start;
- this._onScrollTriggered = false;
-
- if (!this._scrollOffsetRef && this._scrollOffsetRef !== 0) {
- return;
- }
+ this._itemToSnapTo = index;
+ this._scrollOffsetRef =
+ this._positions[index] && this._positions[index].start;
+ this._onScrollTriggered = false;
- this._scrollTo(this._scrollOffsetRef, animated);
-
- this._scrollEndOffset = this._currentContentOffset;
-
- if (enableMomentum) {
- // iOS fix, check the note in the constructor
- if (!initial) {
- this._ignoreNextMomentum = true;
- }
-
- // When momentum is enabled and the user is overscrolling or swiping very quickly,
- // 'onScroll' is not going to be triggered for edge items. Then callback won't be
- // fired and loop won't work since the scrollview is not going to be repositioned.
- // As a workaround, '_onScroll()' will be called manually for these items if a given
- // condition hasn't been met after a small delay.
- // WARNING: this is ok only when relying on 'momentumScrollEnd', not with 'scrollEndDrag'
- if (index === 0 || index === itemsLength - 1) {
- clearTimeout(this._edgeItemTimeout);
- this._edgeItemTimeout = setTimeout(() => {
- if (!initial && index === this._activeItem && !this._onScrollTriggered) {
- this._onScroll();
- }
- }, 250);
- }
- }
+ if (!this._scrollOffsetRef && this._scrollOffsetRef !== 0) {
+ return;
}
- _onBeforeSnap (index) {
- const { onBeforeSnapToItem } = this.props;
+ this._scrollTo(this._scrollOffsetRef, animated);
- if (!this._carouselRef) {
- return;
- }
+ this._scrollEndOffset = this._currentContentOffset;
+
+ if (enableMomentum) {
+ // iOS fix, check the note in the constructor
+ if (!initial) {
+ this._ignoreNextMomentum = true;
+ }
- this._canFireBeforeCallback = false;
- onBeforeSnapToItem && onBeforeSnapToItem(index);
+ // When momentum is enabled and the user is overscrolling or swiping very quickly,
+ // 'onScroll' is not going to be triggered for edge items. Then callback won't be
+ // fired and loop won't work since the scrollview is not going to be repositioned.
+ // As a workaround, '_onScroll()' will be called manually for these items if a given
+ // condition hasn't been met after a small delay.
+ // WARNING: this is ok only when relying on 'momentumScrollEnd', not with 'scrollEndDrag'
+ if (index === 0 || index === itemsLength - 1) {
+ clearTimeout(this._edgeItemTimeout);
+ this._edgeItemTimeout = setTimeout(() => {
+ if (
+ !initial &&
+ index === this._activeItem &&
+ !this._onScrollTriggered
+ ) {
+ this._onScroll();
+ }
+ }, 250);
+ }
}
+ }
- _onSnap (index) {
- const { onSnapToItem } = this.props;
+ _onBeforeSnap(index) {
+ const { onBeforeSnapToItem } = this.props;
- if (!this._carouselRef) {
- return;
- }
-
- this._canFireCallback = false;
- onSnapToItem && onSnapToItem(index);
+ if (!this._carouselRef) {
+ return;
}
- startAutoplay () {
- const { autoplayInterval, autoplayDelay } = this.props;
- this._autoplay = true;
-
- if (this._autoplaying) {
- return;
- }
+ this._canFireBeforeCallback = false;
+ onBeforeSnapToItem && onBeforeSnapToItem(index);
+ }
- clearTimeout(this._autoplayTimeout);
- this._autoplayTimeout = setTimeout(() => {
- this._autoplaying = true;
- this._autoplayInterval = setInterval(() => {
- if (this._autoplaying) {
- this.snapToNext();
- }
- }, autoplayInterval);
- }, autoplayDelay);
- }
+ _onSnap(index) {
+ const { onSnapToItem } = this.props;
- pauseAutoPlay () {
- this._autoplaying = false;
- clearTimeout(this._autoplayTimeout);
- clearTimeout(this._enableAutoplayTimeout);
- clearInterval(this._autoplayInterval);
+ if (!this._carouselRef) {
+ return;
}
- stopAutoplay () {
- this._autoplay = false;
- this.pauseAutoPlay();
- }
+ this._canFireCallback = false;
+ onSnapToItem && onSnapToItem(index);
+ }
- snapToItem (index, animated = true, fireCallback = true) {
- if (!index || index < 0) {
- index = 0;
- }
+ startAutoplay() {
+ const { autoplayInterval, autoplayDelay } = this.props;
+ this._autoplay = true;
- const positionIndex = this._getPositionIndex(index);
+ if (this._autoplaying) {
+ return;
+ }
- if (positionIndex === this._activeItem) {
- return;
+ clearTimeout(this._autoplayTimeout);
+ this._autoplayTimeout = setTimeout(() => {
+ this._autoplaying = true;
+ this._autoplayInterval = setInterval(() => {
+ if (this._autoplaying) {
+ this.snapToNext();
}
+ }, autoplayInterval);
+ }, autoplayDelay);
+ }
- this._snapToItem(positionIndex, animated, fireCallback);
- }
+ pauseAutoPlay() {
+ this._autoplaying = false;
+ clearTimeout(this._autoplayTimeout);
+ clearTimeout(this._enableAutoplayTimeout);
+ clearInterval(this._autoplayInterval);
+ }
- snapToNext (animated = true, fireCallback = true) {
- const itemsLength = this._getCustomDataLength();
+ stopAutoplay() {
+ this._autoplay = false;
+ this.pauseAutoPlay();
+ }
- let newIndex = this._activeItem + 1;
- if (newIndex > itemsLength - 1) {
- if (!this._enableLoop()) {
- return;
- }
- newIndex = 0;
- }
- this._snapToItem(newIndex, animated, fireCallback);
+ snapToItem(index, animated = true, fireCallback = true) {
+ if (!index || index < 0) {
+ index = 0;
}
- snapToPrev (animated = true, fireCallback = true) {
- const itemsLength = this._getCustomDataLength();
+ const positionIndex = this._getPositionIndex(index);
- let newIndex = this._activeItem - 1;
- if (newIndex < 0) {
- if (!this._enableLoop()) {
- return;
- }
- newIndex = itemsLength - 1;
- }
- this._snapToItem(newIndex, animated, fireCallback);
+ if (positionIndex === this._activeItem) {
+ return;
}
- // https://github.com/facebook/react-native/issues/1831#issuecomment-231069668
- triggerRenderingHack (offset) {
- // Avoid messing with user scroll
- if (Date.now() - this._lastScrollDate < 500) {
- return;
- }
+ this._snapToItem(positionIndex, animated, fireCallback);
+ }
- const scrollPosition = this._currentContentOffset;
- if (!scrollPosition && scrollPosition !== 0) {
- return;
- }
+ snapToNext(animated = true, fireCallback = true) {
+ const itemsLength = this._getCustomDataLength();
- const scrollOffset = offset || (scrollPosition === 0 ? 1 : -1);
- this._scrollTo(scrollPosition + scrollOffset, false);
+ let newIndex = this._activeItem + 1;
+ if (newIndex > itemsLength - 1) {
+ if (!this._enableLoop()) {
+ return;
+ }
+ newIndex = 0;
}
+ this._snapToItem(newIndex, animated, fireCallback);
+ }
- _getSlideInterpolatedStyle (index, animatedValue) {
- const { layoutCardOffset, slideInterpolatedStyle } = this.props;
+ snapToPrev(animated = true, fireCallback = true) {
+ const itemsLength = this._getCustomDataLength();
- if (slideInterpolatedStyle) {
- return slideInterpolatedStyle(index, animatedValue, this.props);
- } else if (this._shouldUseTinderLayout()) {
- return tinderAnimatedStyles(index, animatedValue, this.props, layoutCardOffset);
- } else if (this._shouldUseStackLayout()) {
- return stackAnimatedStyles(index, animatedValue, this.props, layoutCardOffset);
- } else if (this._shouldUseShiftLayout()) {
- return shiftAnimatedStyles(index, animatedValue, this.props);
- } else {
- return defaultAnimatedStyles(index, animatedValue, this.props);
- }
+ let newIndex = this._activeItem - 1;
+ if (newIndex < 0) {
+ if (!this._enableLoop()) {
+ return;
+ }
+ newIndex = itemsLength - 1;
}
+ this._snapToItem(newIndex, animated, fireCallback);
+ }
- _renderItem ({ item, index }) {
- const { interpolators } = this.state;
- const {
- hasParallaxImages,
- itemWidth,
- itemHeight,
- keyExtractor,
- renderItem,
- sliderHeight,
- sliderWidth,
- slideStyle,
- vertical
- } = this.props;
-
- const animatedValue = interpolators && interpolators[index];
-
- if (!animatedValue && animatedValue !== 0) {
- return null;
- }
-
- const animate = this._shouldAnimateSlides();
- const Component = animate ? Animated.View : View;
- const animatedStyle = animate ? this._getSlideInterpolatedStyle(index, animatedValue) : {};
-
- const parallaxProps = hasParallaxImages ? {
- scrollPosition: this._scrollPos,
- carouselRef: this._carouselRef,
- vertical,
- sliderWidth,
- sliderHeight,
- itemWidth,
- itemHeight
- } : undefined;
-
- const mainDimension = vertical ? { height: itemHeight } : { width: itemWidth };
- const specificProps = this._needsScrollView() ? {
- key: keyExtractor ? keyExtractor(item, index) : this._getKeyExtractor(item, index)
- } : {};
-
- return (
-
- { renderItem({ item, index }, parallaxProps) }
-
- );
- }
-
- _getComponentOverridableProps () {
- const {
- enableMomentum,
- itemWidth,
- itemHeight,
- loopClonesPerSide,
- sliderWidth,
- sliderHeight,
- vertical
- } = this.props;
-
- const visibleItems = Math.ceil(vertical ?
- sliderHeight / itemHeight :
- sliderWidth / itemWidth) + 1;
- const initialNumPerSide = this._enableLoop() ? loopClonesPerSide : 2;
- const initialNumToRender = visibleItems + (initialNumPerSide * 2);
- const maxToRenderPerBatch = 1 + (initialNumToRender * 2);
- const windowSize = maxToRenderPerBatch;
-
- const specificProps = !this._needsScrollView() ? {
- initialNumToRender: initialNumToRender,
- maxToRenderPerBatch: maxToRenderPerBatch,
- windowSize: windowSize
- // updateCellsBatchingPeriod
- } : {};
-
- return {
- decelerationRate: enableMomentum ? 0.9 : 'fast',
- showsHorizontalScrollIndicator: false,
- showsVerticalScrollIndicator: false,
- overScrollMode: 'never',
- automaticallyAdjustContentInsets: false,
- directionalLockEnabled: true,
- pinchGestureEnabled: false,
- scrollsToTop: false,
- removeClippedSubviews: !this._needsScrollView(),
- inverted: this._needsRTLAdaptations(),
- // renderToHardwareTextureAndroid: true,
- ...specificProps
- };
+ // https://github.com/facebook/react-native/issues/1831#issuecomment-231069668
+ triggerRenderingHack(offset) {
+ // Avoid messing with user scroll
+ if (Date.now() - this._lastScrollDate < 500) {
+ return;
}
- _getComponentStaticProps () {
- const { hideCarousel } = this.state;
- const {
- containerCustomStyle,
- contentContainerCustomStyle,
- keyExtractor,
- sliderWidth,
- sliderHeight,
- style,
- vertical
- } = this.props;
-
- const containerStyle = [
- containerCustomStyle || style || {},
- hideCarousel ? { opacity: 0 } : {},
- vertical ?
- { height: sliderHeight, flexDirection: 'column' } :
- // LTR hack; see https://github.com/facebook/react-native/issues/11960
- // and https://github.com/facebook/react-native/issues/13100#issuecomment-328986423
- { width: sliderWidth, flexDirection: this._needsRTLAdaptations() ? 'row-reverse' : 'row' }
- ];
- const contentContainerStyle = [
- vertical ? {
- paddingTop: this._getContainerInnerMargin(),
- paddingBottom: this._getContainerInnerMargin(true)
- } : {
- paddingLeft: this._getContainerInnerMargin(),
- paddingRight: this._getContainerInnerMargin(true)
- },
- contentContainerCustomStyle || {}
- ];
-
- const specificProps = !this._needsScrollView() ? {
- // extraData: this.state,
- renderItem: this._renderItem,
- numColumns: 1,
- keyExtractor: keyExtractor || this._getKeyExtractor
- } : {};
-
- return {
- ref: c => this._carouselRef = c,
- data: this._getCustomData(),
- style: containerStyle,
- contentContainerStyle: contentContainerStyle,
- horizontal: !vertical,
- scrollEventThrottle: 1,
- onScroll: this._onScrollHandler,
- onScrollBeginDrag: this._onScrollBeginDrag,
- onScrollEndDrag: this._onScrollEndDrag,
- onMomentumScrollEnd: this._onMomentumScrollEnd,
- onResponderRelease: this._onTouchRelease,
- onStartShouldSetResponderCapture: this._onStartShouldSetResponderCapture,
- onTouchStart: this._onTouchStart,
- onTouchEnd: this._onScrollEnd,
- onLayout: this._onLayout,
- ...specificProps
- };
+ const scrollPosition = this._currentContentOffset;
+ if (!scrollPosition && scrollPosition !== 0) {
+ return;
}
- render () {
- const { data, renderItem, useScrollView } = this.props;
+ const scrollOffset = offset || (scrollPosition === 0 ? 1 : -1);
+ this._scrollTo(scrollPosition + scrollOffset, false);
+ }
- if (!data || !renderItem) {
- return null;
- }
+ _getSlideInterpolatedStyle(index, animatedValue) {
+ const { layoutCardOffset, slideInterpolatedStyle } = this.props;
- const props = {
- ...this._getComponentOverridableProps(),
- ...this.props,
- ...this._getComponentStaticProps()
- };
+ if (slideInterpolatedStyle) {
+ return slideInterpolatedStyle(index, animatedValue, this.props);
+ } else if (this._shouldUseTinderLayout()) {
+ return tinderAnimatedStyles(
+ index,
+ animatedValue,
+ this.props,
+ layoutCardOffset
+ );
+ } else if (this._shouldUseStackLayout()) {
+ return stackAnimatedStyles(
+ index,
+ animatedValue,
+ this.props,
+ layoutCardOffset
+ );
+ } else if (this._shouldUseShiftLayout()) {
+ return shiftAnimatedStyles(index, animatedValue, this.props);
+ } else {
+ return defaultAnimatedStyles(index, animatedValue, this.props);
+ }
+ }
+
+ _renderItem({ item, index }) {
+ const { interpolators } = this.state;
+ const {
+ hasParallaxImages,
+ itemWidth,
+ itemHeight,
+ keyExtractor,
+ renderItem,
+ sliderHeight,
+ sliderWidth,
+ slideStyle,
+ vertical,
+ } = this.props;
+
+ const animatedValue = interpolators && interpolators[index];
+
+ if (!animatedValue && animatedValue !== 0) {
+ return null;
+ }
+
+ const animate = this._shouldAnimateSlides();
+ const Component = animate ? Animated.View : View;
+ const animatedStyle = animate
+ ? this._getSlideInterpolatedStyle(index, animatedValue)
+ : {};
+
+ const parallaxProps = hasParallaxImages
+ ? {
+ scrollPosition: this._scrollPos,
+ carouselRef: this._carouselRef,
+ vertical,
+ sliderWidth,
+ sliderHeight,
+ itemWidth,
+ itemHeight,
+ }
+ : undefined;
+
+ const mainDimension = vertical
+ ? { height: itemHeight }
+ : { width: itemWidth };
+ const specificProps = this._needsScrollView()
+ ? {
+ key: keyExtractor
+ ? keyExtractor(item, index)
+ : this._getKeyExtractor(item, index),
+ }
+ : {};
+
+ return (
+
+ {renderItem({ item, index }, parallaxProps)}
+
+ );
+ }
+
+ _getComponentOverridableProps() {
+ const {
+ enableMomentum,
+ itemWidth,
+ itemHeight,
+ loopClonesPerSide,
+ sliderWidth,
+ sliderHeight,
+ vertical,
+ } = this.props;
+
+ const visibleItems =
+ Math.ceil(
+ vertical ? sliderHeight / itemHeight : sliderWidth / itemWidth
+ ) + 1;
+ const initialNumPerSide = this._enableLoop() ? loopClonesPerSide : 2;
+ const initialNumToRender = visibleItems + initialNumPerSide * 2;
+ const maxToRenderPerBatch = 1 + initialNumToRender * 2;
+ const windowSize = maxToRenderPerBatch;
+
+ const specificProps = !this._needsScrollView()
+ ? {
+ initialNumToRender: initialNumToRender,
+ maxToRenderPerBatch: maxToRenderPerBatch,
+ windowSize: windowSize,
+ // updateCellsBatchingPeriod
+ }
+ : {};
+
+ return {
+ decelerationRate: enableMomentum ? 0.9 : "fast",
+ showsHorizontalScrollIndicator: false,
+ showsVerticalScrollIndicator: false,
+ overScrollMode: "never",
+ automaticallyAdjustContentInsets: false,
+ directionalLockEnabled: true,
+ pinchGestureEnabled: false,
+ scrollsToTop: false,
+ removeClippedSubviews: !this._needsScrollView(),
+ inverted: this._needsRTLAdaptations(),
+ // renderToHardwareTextureAndroid: true,
+ ...specificProps,
+ };
+ }
+
+ _getComponentStaticProps() {
+ const { hideCarousel } = this.state;
+ const {
+ containerCustomStyle,
+ contentContainerCustomStyle,
+ keyExtractor,
+ sliderWidth,
+ sliderHeight,
+ style,
+ vertical,
+ } = this.props;
+
+ const containerStyle = [
+ containerCustomStyle || style || {},
+ hideCarousel ? { opacity: 0 } : {},
+ vertical
+ ? { height: sliderHeight, flexDirection: "column" }
+ : // LTR hack; see https://github.com/facebook/react-native/issues/11960
+ // and https://github.com/facebook/react-native/issues/13100#issuecomment-328986423
+ {
+ width: sliderWidth,
+ flexDirection: this._needsRTLAdaptations() ? "row-reverse" : "row",
+ },
+ ];
+ const contentContainerStyle = [
+ vertical
+ ? {
+ paddingTop: this._getContainerInnerMargin(),
+ paddingBottom: this._getContainerInnerMargin(true),
+ }
+ : {
+ paddingLeft: this._getContainerInnerMargin(),
+ paddingRight: this._getContainerInnerMargin(true),
+ },
+ contentContainerCustomStyle || {},
+ ];
+
+ const specificProps = !this._needsScrollView()
+ ? {
+ // extraData: this.state,
+ renderItem: this._renderItem,
+ numColumns: 1,
+ keyExtractor: keyExtractor || this._getKeyExtractor,
+ }
+ : {};
+
+ return {
+ ref: (c) => (this._carouselRef = c),
+ data: this._getCustomData(),
+ style: containerStyle,
+ contentContainerStyle: contentContainerStyle,
+ horizontal: !vertical,
+ scrollEventThrottle: 1,
+ onScroll: this._onScrollHandler,
+ onScrollBeginDrag: this._onScrollBeginDrag,
+ onScrollEndDrag: this._onScrollEndDrag,
+ onMomentumScrollEnd: this._onMomentumScrollEnd,
+ onResponderRelease: this._onTouchRelease,
+ onStartShouldSetResponderCapture: this._onStartShouldSetResponderCapture,
+ onTouchStart: this._onTouchStart,
+ onTouchEnd: this._onScrollEnd,
+ onLayout: this._onLayout,
+ ...specificProps,
+ };
+ }
- const ScrollViewComponent = typeof useScrollView === 'function' ? useScrollView : AnimatedScrollView
+ render() {
+ const { data, renderItem, useScrollView } = this.props;
- return this._needsScrollView() ? (
-
- {
- this._getCustomData().map((item, index) => {
- return this._renderItem({ item, index });
- })
- }
-
- ) : (
-
- );
+ if (!data || !renderItem) {
+ return null;
}
+
+ const props = {
+ ...this._getComponentOverridableProps(),
+ ...this.props,
+ ...this._getComponentStaticProps(),
+ };
+
+ const ScrollViewComponent =
+ typeof useScrollView === "function" ? useScrollView : AnimatedScrollView;
+
+ return this._needsScrollView() ? (
+
+ {this._getCustomData().map((item, index) => {
+ return this._renderItem({ item, index });
+ })}
+
+ ) : (
+
+ );
+ }
}
diff --git a/src/pagination/Pagination.js b/src/pagination/Pagination.js
index 5c021cf36..c9a083f98 100644
--- a/src/pagination/Pagination.js
+++ b/src/pagination/Pagination.js
@@ -1,167 +1,186 @@
-import React, { PureComponent } from 'react';
-import { I18nManager, Platform, View, ViewPropTypes } from 'react-native';
-import PropTypes from 'prop-types';
-import PaginationDot from './PaginationDot';
-import styles from './Pagination.style';
+import React, { PureComponent } from "react";
+import { I18nManager, Platform, View } from "react-native";
+import { ViewPropTypes } from "deprecated-react-native-prop-types";
-const IS_IOS = Platform.OS === 'ios';
+import PropTypes from "prop-types";
+import PaginationDot from "./PaginationDot";
+import styles from "./Pagination.style";
+
+const IS_IOS = Platform.OS === "ios";
const IS_RTL = I18nManager.isRTL;
export default class Pagination extends PureComponent {
-
- static propTypes = {
- activeDotIndex: PropTypes.number.isRequired,
- dotsLength: PropTypes.number.isRequired,
- activeOpacity: PropTypes.number,
- carouselRef: PropTypes.object,
- containerStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
- dotColor: PropTypes.string,
- dotContainerStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
- dotElement: PropTypes.element,
- dotStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
- inactiveDotColor: PropTypes.string,
- inactiveDotElement: PropTypes.element,
- inactiveDotOpacity: PropTypes.number,
- inactiveDotScale: PropTypes.number,
- inactiveDotStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
- renderDots: PropTypes.func,
- tappableDots: PropTypes.bool,
- vertical: PropTypes.bool,
- accessibilityLabel: PropTypes.string,
- animatedDuration: PropTypes.number,
- animatedFriction: PropTypes.number,
- animatedTension: PropTypes.number,
- delayPressInDot: PropTypes.number,
- };
-
- static defaultProps = {
- inactiveDotOpacity: 0.5,
- inactiveDotScale: 0.5,
- tappableDots: false,
- vertical: false,
- animatedDuration: 250,
- animatedFriction: 4,
- animatedTension: 50,
- delayPressInDot: 0,
+ static propTypes = {
+ activeDotIndex: PropTypes.number.isRequired,
+ dotsLength: PropTypes.number.isRequired,
+ activeOpacity: PropTypes.number,
+ carouselRef: PropTypes.object,
+ containerStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
+ dotColor: PropTypes.string,
+ dotContainerStyle: ViewPropTypes
+ ? ViewPropTypes.style
+ : View.propTypes.style,
+ dotElement: PropTypes.element,
+ dotStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
+ inactiveDotColor: PropTypes.string,
+ inactiveDotElement: PropTypes.element,
+ inactiveDotOpacity: PropTypes.number,
+ inactiveDotScale: PropTypes.number,
+ inactiveDotStyle: ViewPropTypes
+ ? ViewPropTypes.style
+ : View.propTypes.style,
+ renderDots: PropTypes.func,
+ tappableDots: PropTypes.bool,
+ vertical: PropTypes.bool,
+ accessibilityLabel: PropTypes.string,
+ animatedDuration: PropTypes.number,
+ animatedFriction: PropTypes.number,
+ animatedTension: PropTypes.number,
+ delayPressInDot: PropTypes.number,
+ };
+
+ static defaultProps = {
+ inactiveDotOpacity: 0.5,
+ inactiveDotScale: 0.5,
+ tappableDots: false,
+ vertical: false,
+ animatedDuration: 250,
+ animatedFriction: 4,
+ animatedTension: 50,
+ delayPressInDot: 0,
+ };
+
+ constructor(props) {
+ super(props);
+
+ // Warnings
+ if (
+ (props.dotColor && !props.inactiveDotColor) ||
+ (!props.dotColor && props.inactiveDotColor)
+ ) {
+ console.warn(
+ "react-native-snap-carousel | Pagination: " +
+ "You need to specify both `dotColor` and `inactiveDotColor`"
+ );
}
-
- constructor (props) {
- super(props);
-
- // Warnings
- if ((props.dotColor && !props.inactiveDotColor) || (!props.dotColor && props.inactiveDotColor)) {
- console.warn(
- 'react-native-snap-carousel | Pagination: ' +
- 'You need to specify both `dotColor` and `inactiveDotColor`'
- );
- }
- if ((props.dotElement && !props.inactiveDotElement) || (!props.dotElement && props.inactiveDotElement)) {
- console.warn(
- 'react-native-snap-carousel | Pagination: ' +
- 'You need to specify both `dotElement` and `inactiveDotElement`'
- );
- }
- if (props.tappableDots && props.carouselRef === undefined) {
- console.warn(
- 'react-native-snap-carousel | Pagination: ' +
- 'You must specify prop `carouselRef` when setting `tappableDots` to `true`'
- );
- }
+ if (
+ (props.dotElement && !props.inactiveDotElement) ||
+ (!props.dotElement && props.inactiveDotElement)
+ ) {
+ console.warn(
+ "react-native-snap-carousel | Pagination: " +
+ "You need to specify both `dotElement` and `inactiveDotElement`"
+ );
}
-
- _needsRTLAdaptations () {
- const { vertical } = this.props;
- return IS_RTL && !IS_IOS && !vertical;
+ if (props.tappableDots && props.carouselRef === undefined) {
+ console.warn(
+ "react-native-snap-carousel | Pagination: " +
+ "You must specify prop `carouselRef` when setting `tappableDots` to `true`"
+ );
}
-
- get _activeDotIndex () {
- const { activeDotIndex, dotsLength } = this.props;
- return this._needsRTLAdaptations() ? dotsLength - activeDotIndex - 1 : activeDotIndex;
+ }
+
+ _needsRTLAdaptations() {
+ const { vertical } = this.props;
+ return IS_RTL && !IS_IOS && !vertical;
+ }
+
+ get _activeDotIndex() {
+ const { activeDotIndex, dotsLength } = this.props;
+ return this._needsRTLAdaptations()
+ ? dotsLength - activeDotIndex - 1
+ : activeDotIndex;
+ }
+
+ get dots() {
+ const {
+ activeOpacity,
+ carouselRef,
+ dotsLength,
+ dotColor,
+ dotContainerStyle,
+ dotElement,
+ dotStyle,
+ inactiveDotColor,
+ inactiveDotElement,
+ inactiveDotOpacity,
+ inactiveDotScale,
+ inactiveDotStyle,
+ renderDots,
+ tappableDots,
+ animatedDuration,
+ animatedFriction,
+ animatedTension,
+ delayPressInDot,
+ } = this.props;
+
+ if (renderDots) {
+ return renderDots(this._activeDotIndex, dotsLength, this);
}
- get dots () {
- const {
- activeOpacity,
- carouselRef,
- dotsLength,
- dotColor,
- dotContainerStyle,
- dotElement,
- dotStyle,
- inactiveDotColor,
- inactiveDotElement,
- inactiveDotOpacity,
- inactiveDotScale,
- inactiveDotStyle,
- renderDots,
- tappableDots,
- animatedDuration,
- animatedFriction,
- animatedTension,
- delayPressInDot,
- } = this.props;
-
- if (renderDots) {
- return renderDots(this._activeDotIndex, dotsLength, this);
+ const DefaultDot = (
+
+ );
+
+ const dots = [...Array(dotsLength).keys()].map((i) => {
+ const isActive = i === this._activeDotIndex;
+ return React.cloneElement(
+ (isActive ? dotElement : inactiveDotElement) || DefaultDot,
+ {
+ key: `pagination-dot-${i}`,
+ active: isActive,
+ index: i,
}
+ );
+ });
- const DefaultDot = ;
-
- const dots = [...Array(dotsLength).keys()].map(i => {
- const isActive = i === this._activeDotIndex;
- return React.cloneElement(
- (isActive ? dotElement : inactiveDotElement) || DefaultDot,
- {
- key: `pagination-dot-${i}`,
- active: isActive,
- index: i
- }
- );
- });
-
- return dots;
- }
-
- render () {
- const { dotsLength, containerStyle, vertical, accessibilityLabel } = this.props;
+ return dots;
+ }
- if (!dotsLength || dotsLength < 2) {
- return false;
- }
+ render() {
+ const { dotsLength, containerStyle, vertical, accessibilityLabel } =
+ this.props;
- const style = [
- styles.sliderPagination,
- { flexDirection: vertical ?
- 'column' :
- (this._needsRTLAdaptations() ? 'row-reverse' : 'row')
- },
- containerStyle || {}
- ];
-
- return (
-
- { this.dots }
-
- );
+ if (!dotsLength || dotsLength < 2) {
+ return false;
}
+
+ const style = [
+ styles.sliderPagination,
+ {
+ flexDirection: vertical
+ ? "column"
+ : this._needsRTLAdaptations()
+ ? "row-reverse"
+ : "row",
+ },
+ containerStyle || {},
+ ];
+
+ return (
+
+ {this.dots}
+
+ );
+ }
}
diff --git a/src/pagination/PaginationDot.js b/src/pagination/PaginationDot.js
index e59d1969f..30e853b14 100644
--- a/src/pagination/PaginationDot.js
+++ b/src/pagination/PaginationDot.js
@@ -1,156 +1,166 @@
-import React, { PureComponent } from 'react';
-import { View, Animated, Easing, TouchableOpacity, ViewPropTypes } from 'react-native';
-import PropTypes from 'prop-types';
-import styles from './Pagination.style';
+import React, { PureComponent } from "react";
+import { View, Animated, Easing, TouchableOpacity } from "react-native";
+import { ViewPropTypes } from "deprecated-react-native-prop-types";
-export default class PaginationDot extends PureComponent {
+import PropTypes from "prop-types";
+import styles from "./Pagination.style";
- static propTypes = {
- inactiveOpacity: PropTypes.number.isRequired,
- inactiveScale: PropTypes.number.isRequired,
- active: PropTypes.bool,
- activeOpacity: PropTypes.number,
- carouselRef: PropTypes.object,
- color: PropTypes.string,
- containerStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
- inactiveColor: PropTypes.string,
- inactiveStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
- index: PropTypes.number,
- style: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
- tappable: PropTypes.bool
+export default class PaginationDot extends PureComponent {
+ static propTypes = {
+ inactiveOpacity: PropTypes.number.isRequired,
+ inactiveScale: PropTypes.number.isRequired,
+ active: PropTypes.bool,
+ activeOpacity: PropTypes.number,
+ carouselRef: PropTypes.object,
+ color: PropTypes.string,
+ containerStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
+ inactiveColor: PropTypes.string,
+ inactiveStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
+ index: PropTypes.number,
+ style: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
+ tappable: PropTypes.bool,
+ };
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ animColor: new Animated.Value(0),
+ animOpacity: new Animated.Value(0),
+ animTransform: new Animated.Value(0),
};
+ }
- constructor (props) {
- super(props);
- this.state = {
- animColor: new Animated.Value(0),
- animOpacity: new Animated.Value(0),
- animTransform: new Animated.Value(0)
- };
+ componentDidMount() {
+ if (this.props.active) {
+ this._animate(1);
}
+ }
- componentDidMount () {
- if (this.props.active) {
- this._animate(1);
- }
+ componentDidUpdate(prevProps) {
+ if (prevProps.active !== this.props.active) {
+ this._animate(this.props.active ? 1 : 0);
}
+ }
- componentDidUpdate (prevProps) {
- if (prevProps.active !== this.props.active) {
- this._animate(this.props.active ? 1 : 0);
- }
- }
-
- _animate (toValue = 0) {
- const { animColor, animOpacity, animTransform } = this.state;
- const { animatedDuration, animatedFriction, animatedTension } = this.props
-
- const commonProperties = {
- toValue,
- duration: animatedDuration,
- isInteraction: false,
- useNativeDriver: !this._shouldAnimateColor
- };
-
- let animations = [
- Animated.timing(animOpacity, {
- easing: Easing.linear,
- ...commonProperties
- }),
- Animated.spring(animTransform, {
- friction: animatedFriction,
- tension: animatedTension,
- ...commonProperties
- })
- ];
-
- if (this._shouldAnimateColor) {
- animations.push(Animated.timing(animColor, {
- easing: Easing.linear,
- ...commonProperties
- }));
- }
+ _animate(toValue = 0) {
+ const { animColor, animOpacity, animTransform } = this.state;
+ const { animatedDuration, animatedFriction, animatedTension } = this.props;
- Animated.parallel(animations).start();
- }
+ const commonProperties = {
+ toValue,
+ duration: animatedDuration,
+ isInteraction: false,
+ useNativeDriver: !this._shouldAnimateColor,
+ };
- get _shouldAnimateColor () {
- const { color, inactiveColor } = this.props;
- return color && inactiveColor;
+ let animations = [
+ Animated.timing(animOpacity, {
+ easing: Easing.linear,
+ ...commonProperties,
+ }),
+ Animated.spring(animTransform, {
+ friction: animatedFriction,
+ tension: animatedTension,
+ ...commonProperties,
+ }),
+ ];
+
+ if (this._shouldAnimateColor) {
+ animations.push(
+ Animated.timing(animColor, {
+ easing: Easing.linear,
+ ...commonProperties,
+ })
+ );
}
- render () {
- const { animColor, animOpacity, animTransform } = this.state;
- const {
- active,
- activeOpacity,
- carouselRef,
- color,
- containerStyle,
- inactiveColor,
- inactiveStyle,
- inactiveOpacity,
- inactiveScale,
- index,
- style,
- tappable,
- delayPressInDot
- } = this.props;
-
- const animatedStyle = {
- opacity: animOpacity.interpolate({
- inputRange: [0, 1],
- outputRange: [inactiveOpacity, 1]
- }),
- transform: [{
- scale: animTransform.interpolate({
- inputRange: [0, 1],
- outputRange: [inactiveScale, 1]
- })
- }]
- };
- const animatedColor = this._shouldAnimateColor ? {
- backgroundColor: animColor.interpolate({
- inputRange: [0, 1],
- outputRange: [inactiveColor, color]
- })
- } : {};
-
- const dotContainerStyle = [
- styles.sliderPaginationDotContainer,
- containerStyle || {}
- ];
-
- const dotStyle = [
- styles.sliderPaginationDot,
- style || {},
- (!active && inactiveStyle) || {},
- animatedStyle,
- animatedColor
- ];
-
- const onPress = tappable ? () => {
- try {
- const currentRef = carouselRef.current || carouselRef;
- currentRef._snapToItem(currentRef._getPositionIndex(index));
- } catch (error) {
- console.warn(
- 'react-native-snap-carousel | Pagination: ' +
- '`carouselRef` has to be a Carousel ref.\n' + error
- );
- }
- } : undefined;
-
- return (
-
-
-
- );
- }
+ Animated.parallel(animations).start();
+ }
+
+ get _shouldAnimateColor() {
+ const { color, inactiveColor } = this.props;
+ return color && inactiveColor;
+ }
+
+ render() {
+ const { animColor, animOpacity, animTransform } = this.state;
+ const {
+ active,
+ activeOpacity,
+ carouselRef,
+ color,
+ containerStyle,
+ inactiveColor,
+ inactiveStyle,
+ inactiveOpacity,
+ inactiveScale,
+ index,
+ style,
+ tappable,
+ delayPressInDot,
+ } = this.props;
+
+ const animatedStyle = {
+ opacity: animOpacity.interpolate({
+ inputRange: [0, 1],
+ outputRange: [inactiveOpacity, 1],
+ }),
+ transform: [
+ {
+ scale: animTransform.interpolate({
+ inputRange: [0, 1],
+ outputRange: [inactiveScale, 1],
+ }),
+ },
+ ],
+ };
+ const animatedColor = this._shouldAnimateColor
+ ? {
+ backgroundColor: animColor.interpolate({
+ inputRange: [0, 1],
+ outputRange: [inactiveColor, color],
+ }),
+ }
+ : {};
+
+ const dotContainerStyle = [
+ styles.sliderPaginationDotContainer,
+ containerStyle || {},
+ ];
+
+ const dotStyle = [
+ styles.sliderPaginationDot,
+ style || {},
+ (!active && inactiveStyle) || {},
+ animatedStyle,
+ animatedColor,
+ ];
+
+ const onPress = tappable
+ ? () => {
+ try {
+ const currentRef = carouselRef.current || carouselRef;
+ currentRef._snapToItem(currentRef._getPositionIndex(index));
+ } catch (error) {
+ console.warn(
+ "react-native-snap-carousel | Pagination: " +
+ "`carouselRef` has to be a Carousel ref.\n" +
+ error
+ );
+ }
+ }
+ : undefined;
+
+ return (
+
+
+
+ );
+ }
}
diff --git a/src/parallaximage/ParallaxImage.js b/src/parallaximage/ParallaxImage.js
index 8bc774a10..71362aa41 100644
--- a/src/parallaximage/ParallaxImage.js
+++ b/src/parallaximage/ParallaxImage.js
@@ -1,222 +1,242 @@
// Parallax effect inspired by https://github.com/oblador/react-native-parallax/
-import React, { Component } from 'react';
-import { View, ViewPropTypes, Image, Animated, Easing, ActivityIndicator, findNodeHandle } from 'react-native';
-import PropTypes from 'prop-types';
-import styles from './ParallaxImage.style';
+import React, { Component } from "react";
+import {
+ View,
+ Image,
+ Animated,
+ Easing,
+ ActivityIndicator,
+ findNodeHandle,
+} from "react-native";
+import { ViewPropTypes } from "deprecated-react-native-prop-types";
+
+import PropTypes from "prop-types";
+import styles from "./ParallaxImage.style";
export default class ParallaxImage extends Component {
-
- static propTypes = {
- ...Image.propTypes,
- carouselRef: PropTypes.object, // passed from
- itemHeight: PropTypes.number, // passed from
- itemWidth: PropTypes.number, // passed from
- scrollPosition: PropTypes.object, // passed from
- sliderHeight: PropTypes.number, // passed from
- sliderWidth: PropTypes.number, // passed from
- vertical: PropTypes.bool, // passed from
- containerStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
- dimensions: PropTypes.shape({
- width: PropTypes.number,
- height: PropTypes.number
- }),
- fadeDuration: PropTypes.number,
- parallaxFactor: PropTypes.number,
- showSpinner: PropTypes.bool,
- spinnerColor: PropTypes.string,
- AnimatedImageComponent: PropTypes.oneOfType([
- PropTypes.func,
- PropTypes.object
- ])
+ static propTypes = {
+ ...Image.propTypes,
+ carouselRef: PropTypes.object, // passed from
+ itemHeight: PropTypes.number, // passed from
+ itemWidth: PropTypes.number, // passed from
+ scrollPosition: PropTypes.object, // passed from
+ sliderHeight: PropTypes.number, // passed from
+ sliderWidth: PropTypes.number, // passed from
+ vertical: PropTypes.bool, // passed from
+ containerStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
+ dimensions: PropTypes.shape({
+ width: PropTypes.number,
+ height: PropTypes.number,
+ }),
+ fadeDuration: PropTypes.number,
+ parallaxFactor: PropTypes.number,
+ showSpinner: PropTypes.bool,
+ spinnerColor: PropTypes.string,
+ AnimatedImageComponent: PropTypes.oneOfType([
+ PropTypes.func,
+ PropTypes.object,
+ ]),
+ };
+
+ static defaultProps = {
+ containerStyle: {},
+ fadeDuration: 500,
+ parallaxFactor: 0.3,
+ showSpinner: true,
+ spinnerColor: "rgba(0, 0, 0, 0.4)",
+ AnimatedImageComponent: Animated.Image,
+ };
+
+ constructor(props) {
+ super(props);
+ this.state = {
+ offset: 0,
+ width: 0,
+ height: 0,
+ status: 1, // 1 -> loading; 2 -> loaded // 3 -> transition finished; 4 -> error
+ animOpacity: new Animated.Value(0),
};
-
- static defaultProps = {
- containerStyle: {},
- fadeDuration: 500,
- parallaxFactor: 0.3,
- showSpinner: true,
- spinnerColor: 'rgba(0, 0, 0, 0.4)',
- AnimatedImageComponent: Animated.Image
- }
-
- constructor (props) {
- super(props);
- this.state = {
- offset: 0,
- width: 0,
- height: 0,
- status: 1, // 1 -> loading; 2 -> loaded // 3 -> transition finished; 4 -> error
- animOpacity: new Animated.Value(0)
- };
- this._onLoad = this._onLoad.bind(this);
- this._onError = this._onError.bind(this);
- this._measureLayout = this._measureLayout.bind(this);
- }
-
- setNativeProps (nativeProps) {
- this._container.setNativeProps(nativeProps);
- }
-
- componentDidMount () {
- this._mounted = true;
-
- setTimeout(() => {
- this._measureLayout();
- }, 0);
- }
-
- componentWillUnmount () {
- this._mounted = false;
+ this._onLoad = this._onLoad.bind(this);
+ this._onError = this._onError.bind(this);
+ this._measureLayout = this._measureLayout.bind(this);
+ }
+
+ setNativeProps(nativeProps) {
+ this._container.setNativeProps(nativeProps);
+ }
+
+ componentDidMount() {
+ this._mounted = true;
+
+ setTimeout(() => {
+ this._measureLayout();
+ }, 0);
+ }
+
+ componentWillUnmount() {
+ this._mounted = false;
+ }
+
+ _measureLayout() {
+ if (this._container) {
+ const {
+ dimensions,
+ vertical,
+ carouselRef,
+ sliderWidth,
+ sliderHeight,
+ itemWidth,
+ itemHeight,
+ } = this.props;
+
+ if (carouselRef) {
+ this._container.measureLayout(
+ findNodeHandle(carouselRef),
+ (x, y, width, height, pageX, pageY) => {
+ const offset = vertical
+ ? y - (sliderHeight - itemHeight) / 2
+ : x - (sliderWidth - itemWidth) / 2;
+
+ this.setState({
+ offset: offset,
+ width:
+ dimensions && dimensions.width
+ ? dimensions.width
+ : Math.ceil(width),
+ height:
+ dimensions && dimensions.height
+ ? dimensions.height
+ : Math.ceil(height),
+ });
+ }
+ );
+ }
}
+ }
- _measureLayout () {
- if (this._container) {
- const {
- dimensions,
- vertical,
- carouselRef,
- sliderWidth,
- sliderHeight,
- itemWidth,
- itemHeight
- } = this.props;
-
- if (carouselRef) {
- this._container.measureLayout(
- findNodeHandle(carouselRef),
- (x, y, width, height, pageX, pageY) => {
- const offset = vertical ?
- y - ((sliderHeight - itemHeight) / 2) :
- x - ((sliderWidth - itemWidth) / 2);
-
- this.setState({
- offset: offset,
- width: dimensions && dimensions.width ?
- dimensions.width :
- Math.ceil(width),
- height: dimensions && dimensions.height ?
- dimensions.height :
- Math.ceil(height)
- });
- }
- );
- }
- }
- }
+ _onLoad(event) {
+ const { animOpacity } = this.state;
+ const { fadeDuration, onLoad } = this.props;
- _onLoad (event) {
- const { animOpacity } = this.state;
- const { fadeDuration, onLoad } = this.props;
-
- if (!this._mounted) {
- return;
- }
-
- this.setState({ status: 2 });
-
- if (onLoad) {
- onLoad(event);
- }
-
- Animated.timing(animOpacity, {
- toValue: 1,
- duration: fadeDuration,
- easing: Easing.out(Easing.quad),
- isInteraction: false,
- useNativeDriver: true
- }).start(() => {
- this.setState({ status: 3 });
- });
+ if (!this._mounted) {
+ return;
}
- // If arg is missing from method signature, it just won't be called
- _onError (event) {
- const { onError } = this.props;
-
- this.setState({ status: 4 });
-
- if (onError) {
- onError(event);
- }
- }
+ this.setState({ status: 2 });
- get image () {
- const { status, animOpacity, offset, width, height } = this.state;
- const {
- scrollPosition,
- dimensions,
- vertical,
- sliderWidth,
- sliderHeight,
- parallaxFactor,
- style,
- AnimatedImageComponent,
- ...other
- } = this.props;
-
- const parallaxPadding = (vertical ? height : width) * parallaxFactor;
- const requiredStyles = { position: 'relative' };
- const dynamicStyles = {
- width: vertical ? width : width + parallaxPadding * 2,
- height: vertical ? height + parallaxPadding * 2 : height,
- opacity: animOpacity,
- transform: scrollPosition ? [
- {
- translateX: !vertical ? scrollPosition.interpolate({
- inputRange: [offset - sliderWidth, offset + sliderWidth],
- outputRange: [-parallaxPadding, parallaxPadding],
- extrapolate: 'clamp'
- }) : 0
- },
- {
- translateY: vertical ? scrollPosition.interpolate({
- inputRange: [offset - sliderHeight, offset + sliderHeight],
- outputRange: [-parallaxPadding, parallaxPadding],
- extrapolate: 'clamp'
- }) : 0
- }
- ] : []
- };
-
- return (
-
- );
+ if (onLoad) {
+ onLoad(event);
}
- get spinner () {
- const { status } = this.state;
- const { showSpinner, spinnerColor } = this.props;
-
- return status === 1 && showSpinner ? (
-
-
-
- ) : false;
+ Animated.timing(animOpacity, {
+ toValue: 1,
+ duration: fadeDuration,
+ easing: Easing.out(Easing.quad),
+ isInteraction: false,
+ useNativeDriver: true,
+ }).start(() => {
+ this.setState({ status: 3 });
+ });
+ }
+
+ // If arg is missing from method signature, it just won't be called
+ _onError(event) {
+ const { onError } = this.props;
+
+ this.setState({ status: 4 });
+
+ if (onError) {
+ onError(event);
}
+ }
+
+ get image() {
+ const { status, animOpacity, offset, width, height } = this.state;
+ const {
+ scrollPosition,
+ dimensions,
+ vertical,
+ sliderWidth,
+ sliderHeight,
+ parallaxFactor,
+ style,
+ AnimatedImageComponent,
+ ...other
+ } = this.props;
+
+ const parallaxPadding = (vertical ? height : width) * parallaxFactor;
+ const requiredStyles = { position: "relative" };
+ const dynamicStyles = {
+ width: vertical ? width : width + parallaxPadding * 2,
+ height: vertical ? height + parallaxPadding * 2 : height,
+ opacity: animOpacity,
+ transform: scrollPosition
+ ? [
+ {
+ translateX: !vertical
+ ? scrollPosition.interpolate({
+ inputRange: [offset - sliderWidth, offset + sliderWidth],
+ outputRange: [-parallaxPadding, parallaxPadding],
+ extrapolate: "clamp",
+ })
+ : 0,
+ },
+ {
+ translateY: vertical
+ ? scrollPosition.interpolate({
+ inputRange: [offset - sliderHeight, offset + sliderHeight],
+ outputRange: [-parallaxPadding, parallaxPadding],
+ extrapolate: "clamp",
+ })
+ : 0,
+ },
+ ]
+ : [],
+ };
- render () {
- const { containerStyle } = this.props;
-
- return (
- { this._container = c; }}
- pointerEvents={'none'}
- style={[containerStyle, styles.container]}
- onLayout={this._measureLayout}
- >
- { this.image }
- { this.spinner }
-
- );
- }
+ return (
+
+ );
+ }
+
+ get spinner() {
+ const { status } = this.state;
+ const { showSpinner, spinnerColor } = this.props;
+
+ return status === 1 && showSpinner ? (
+
+
+
+ ) : (
+ false
+ );
+ }
+
+ render() {
+ const { containerStyle } = this.props;
+
+ return (
+ {
+ this._container = c;
+ }}
+ pointerEvents={"none"}
+ style={[containerStyle, styles.container]}
+ onLayout={this._measureLayout}
+ >
+ {this.image}
+ {this.spinner}
+
+ );
+ }
}