react-native-image-gallery
Version:
Pure JavaScript image gallery component for iOS and Android
292 lines (261 loc) • 11.6 kB
JavaScript
import React, { PureComponent } from 'react';
import { View, ViewPropTypes } from 'react-native';
import PropTypes from 'prop-types';
import { createResponder } from './libraries/GestureResponder';
import TransformableImage from './libraries/TransformableImage';
import ViewPager from './libraries/ViewPager';
const DEFAULT_FLAT_LIST_PROPS = {
windowSize: 3
};
export default class Gallery extends PureComponent {
static propTypes = {
...View.propTypes,
images: PropTypes.arrayOf(PropTypes.object),
initialPage: PropTypes.number,
scrollViewStyle: ViewPropTypes ? ViewPropTypes.style : View.propTypes.style,
pageMargin: PropTypes.number,
onPageSelected: PropTypes.func,
onPageScrollStateChanged: PropTypes.func,
onPageScroll: PropTypes.func,
onSingleTapConfirmed: PropTypes.func,
onGalleryStateChanged: PropTypes.func,
onLongPress: PropTypes.func,
removeClippedSubviews: PropTypes.bool,
imageComponent: PropTypes.func,
errorComponent: PropTypes.func,
flatListProps: PropTypes.object
};
static defaultProps = {
removeClippedSubviews: true,
imageComponent: undefined,
scrollViewStyle: {},
flatListProps: DEFAULT_FLAT_LIST_PROPS
};
imageRefs = new Map();
activeResponder = undefined;
firstMove = true;
currentPage = 0;
pageCount = 0;
gestureResponder = undefined;
constructor (props) {
super(props);
this.renderPage = this.renderPage.bind(this);
this.onPageSelected = this.onPageSelected.bind(this);
this.onPageScrollStateChanged = this.onPageScrollStateChanged.bind(this);
this.getViewPagerInstance = this.getViewPagerInstance.bind(this);
this.getCurrentImageTransformer = this.getCurrentImageTransformer.bind(this);
this.getImageTransformer = this.getImageTransformer.bind(this);
this.getViewPagerInstance = this.getViewPagerInstance.bind(this);
this.activeImageResponder = this.activeImageResponder.bind(this);
}
componentWillMount () {
let onResponderReleaseOrTerminate = (evt, gestureState) => {
if (this.activeResponder) {
if (this.activeResponder === this.viewPagerResponder &&
!this.shouldScrollViewPager(evt, gestureState) &&
Math.abs(gestureState.vx) > 0.5) {
this.activeResponder.onEnd(evt, gestureState, true);
this.getViewPagerInstance().flingToPage(this.currentPage, gestureState.vx);
} else {
this.activeResponder.onEnd(evt, gestureState);
}
this.activeResponder = null;
}
this.firstMove = true;
this.props.onGalleryStateChanged && this.props.onGalleryStateChanged(true);
};
this.gestureResponder = createResponder({
onStartShouldSetResponderCapture: (evt, gestureState) => true,
onStartShouldSetResponder: (evt, gestureState) => true,
onResponderGrant: this.activeImageResponder,
onResponderMove: (evt, gestureState) => {
if (this.firstMove) {
this.firstMove = false;
if (this.shouldScrollViewPager(evt, gestureState)) {
this.activeViewPagerResponder(evt, gestureState);
}
this.props.onGalleryStateChanged && this.props.onGalleryStateChanged(false);
}
if (this.activeResponder === this.viewPagerResponder) {
const dx = gestureState.moveX - gestureState.previousMoveX;
const offset = this.getViewPagerInstance().getScrollOffsetFromCurrentPage();
if (dx > 0 && offset > 0 && !this.shouldScrollViewPager(evt, gestureState)) {
if (dx > offset) { // active image responder
this.getViewPagerInstance().scrollByOffset(offset);
gestureState.moveX -= offset;
this.activeImageResponder(evt, gestureState);
}
} else if (dx < 0 && offset < 0 && !this.shouldScrollViewPager(evt, gestureState)) {
if (dx < offset) { // active image responder
this.getViewPagerInstance().scrollByOffset(offset);
gestureState.moveX -= offset;
this.activeImageResponder(evt, gestureState);
}
}
}
this.activeResponder.onMove(evt, gestureState);
},
onResponderRelease: onResponderReleaseOrTerminate,
onResponderTerminate: onResponderReleaseOrTerminate,
onResponderTerminationRequest: (evt, gestureState) => false, // Do not allow parent view to intercept gesture
onResponderSingleTapConfirmed: (evt, gestureState) => {
this.props.onSingleTapConfirmed && this.props.onSingleTapConfirmed(this.currentPage);
}
});
this.viewPagerResponder = {
onStart: (evt, gestureState) => {
this.getViewPagerInstance().onResponderGrant(evt, gestureState);
},
onMove: (evt, gestureState) => {
this.getViewPagerInstance().onResponderMove(evt, gestureState);
},
onEnd: (evt, gestureState, disableSettle) => {
this.getViewPagerInstance().onResponderRelease(evt, gestureState, disableSettle);
}
};
this.imageResponder = {
onStart: (evt, gestureState) => {
const currentImageTransformer = this.getCurrentImageTransformer();
currentImageTransformer && currentImageTransformer.onResponderGrant(evt, gestureState);
if (this.props.onLongPress) {
this._longPressTimeout = setTimeout(() => {
this.props.onLongPress(gestureState);
}, 600);
}
},
onMove: (evt, gestureState) => {
const currentImageTransformer = this.getCurrentImageTransformer();
currentImageTransformer && currentImageTransformer.onResponderMove(evt, gestureState);
clearTimeout(this._longPressTimeout);
},
onEnd: (evt, gestureState) => {
const currentImageTransformer = this.getCurrentImageTransformer();
currentImageTransformer && currentImageTransformer.onResponderRelease(evt, gestureState);
clearTimeout(this._longPressTimeout);
}
};
}
componentDidMount () {
this._isMounted = true;
}
componentWillUnmount () {
this._isMounted = false;
}
shouldScrollViewPager (evt, gestureState) {
if (gestureState.numberActiveTouches > 1) {
return false;
}
const viewTransformer = this.getCurrentImageTransformer();
if (!viewTransformer) {
return false;
}
const space = viewTransformer.getAvailableTranslateSpace();
const dx = gestureState.moveX - gestureState.previousMoveX;
if (dx > 0 && space.left <= 0 && this.currentPage > 0) {
return true;
}
if (dx < 0 && space.right <= 0 && this.currentPage < this.pageCount - 1) {
return true;
}
return false;
}
activeImageResponder (evt, gestureState) {
if (this.activeResponder !== this.imageResponder) {
if (this.activeResponder === this.viewPagerResponder) {
this.viewPagerResponder.onEnd(evt, gestureState, true); // pass true to disable ViewPager settle
}
this.activeResponder = this.imageResponder;
this.imageResponder.onStart(evt, gestureState);
}
}
activeViewPagerResponder (evt, gestureState) {
if (this.activeResponder !== this.viewPagerResponder) {
if (this.activeResponder === this.imageResponder) {
this.imageResponder.onEnd(evt, gestureState);
}
this.activeResponder = this.viewPagerResponder;
this.viewPagerResponder.onStart(evt, gestureState);
}
}
getImageTransformer (page) {
if (page >= 0 && page < this.pageCount) {
let ref = this.imageRefs.get(page);
if (ref) {
return ref.getViewTransformerInstance();
}
}
}
getCurrentImageTransformer () {
return this.getImageTransformer(this.currentPage);
}
getViewPagerInstance () {
return this.refs['galleryViewPager'];
}
onPageSelected (page) {
this.currentPage = page;
this.props.onPageSelected && this.props.onPageSelected(page);
}
onPageScrollStateChanged (state) {
if (state === 'idle') {
this.resetHistoryImageTransform();
}
this.props.onPageScrollStateChanged && this.props.onPageScrollStateChanged(state);
}
renderPage (pageData, pageId) {
const { onViewTransformed, onTransformGestureReleased, errorComponent, imageComponent } = this.props;
return (
<TransformableImage
onViewTransformed={((transform) => {
onViewTransformed && onViewTransformed(transform, pageId);
})}
onTransformGestureReleased={((transform) => {
// need the 'return' here because the return value is checked in ViewTransformer
return onTransformGestureReleased && onTransformGestureReleased(transform, pageId);
})}
ref={((ref) => { this.imageRefs.set(pageId, ref); })}
key={'innerImage#' + pageId}
errorComponent={errorComponent}
imageComponent={imageComponent}
image={pageData}
/>
);
}
resetHistoryImageTransform () {
let transformer = this.getImageTransformer(this.currentPage + 1);
if (transformer) {
transformer.forceUpdateTransform({scale: 1, translateX: 0, translateY: 0});
}
transformer = this.getImageTransformer(this.currentPage - 1);
if (transformer) {
transformer.forceUpdateTransform({scale: 1, translateX: 0, translateY: 0});
}
}
render () {
let gestureResponder = this.gestureResponder;
let images = this.props.images;
if (!images) {
images = [];
}
this.pageCount = images.length;
if (this.pageCount <= 0) {
gestureResponder = {};
}
const flatListProps = {...DEFAULT_FLAT_LIST_PROPS, ...this.props.flatListProps};
return (
<ViewPager
{...this.props}
flatListProps={flatListProps}
ref={'galleryViewPager'}
scrollViewStyle={this.props.scrollViewStyle}
scrollEnabled={false}
renderPage={this.renderPage}
pageDataArray={images}
{...gestureResponder}
onPageSelected={this.onPageSelected}
onPageScrollStateChanged={this.onPageScrollStateChanged}
onPageScroll={this.props.onPageScroll}
removeClippedSubviews={this.props.removeClippedSubviews}
/>
);
}
}