diff --git a/README.md b/README.md
index 930d538..0a0889a 100755
--- a/README.md
+++ b/README.md
@@ -91,6 +91,9 @@ The document is being written, please refer to the example source code.
## TransformView
![](https://github.com/rilyu/teaset/blob/master/screenshots/14-TransformView.png?raw=true)
+## AlbumView
+![](https://github.com/rilyu/teaset/blob/master/screenshots/14a-AlbumView1.png?raw=true) ![](https://github.com/rilyu/teaset/blob/master/screenshots/14a-AlbumView2.png?raw=true)
+
## Overlay
![](https://github.com/rilyu/teaset/blob/master/screenshots/15-Overlay1.png?raw=true) ![](https://github.com/rilyu/teaset/blob/master/screenshots/15-Overlay2.png?raw=true)
![](https://github.com/rilyu/teaset/blob/master/screenshots/15-Overlay3.png?raw=true) ![](https://github.com/rilyu/teaset/blob/master/screenshots/15-Overlay6.png?raw=true)
diff --git a/components/AlbumView/AlbumView.js b/components/AlbumView/AlbumView.js
new file mode 100644
index 0000000..5a881a8
--- /dev/null
+++ b/components/AlbumView/AlbumView.js
@@ -0,0 +1,467 @@
+// AlbumView.js
+
+'use strict';
+
+import React, {Component} from "react";
+import PropTypes from 'prop-types';
+import {StyleSheet, View, Image, Animated} from 'react-native';
+import resolveAssetSource from 'resolveAssetSource';
+
+import Theme from 'teaset/themes/Theme';
+import TransformView from '../TransformView/TransformView';
+import CarouselControl from '../Carousel/CarouselControl';
+
+export default class AlbumView extends Component {
+
+ static propTypes = {
+ ...View.propTypes,
+ images: PropTypes.arrayOf(Image.propTypes.source).isRequired,
+ thumbs: PropTypes.arrayOf(Image.propTypes.source),
+ defaultIndex: PropTypes.number,
+ index: PropTypes.number,
+ maxScale: PropTypes.number,
+ space: PropTypes.number,
+ control: PropTypes.oneOfType([PropTypes.bool, PropTypes.element]),
+ onChange: PropTypes.func, //(index, oldIndex)
+ onPress: PropTypes.func, //(index, event)
+ onLongPress: PropTypes.func, //(index, event)
+ onWillLoadImage: PropTypes.func, //(index)
+ onLoadImageSuccess: PropTypes.func, //(index, width, height)
+ onLoadImageFailure: PropTypes.func, //(index, error)
+ };
+
+ static defaultProps = {
+ ...View.defaultProps,
+ defaultIndex: 0,
+ maxScale: 3,
+ space: 20,
+ control: false,
+ };
+
+ static Control = CarouselControl;
+
+ constructor(props) {
+ super(props);
+ let index = props.index || props.index === 0 ? props.index : props.defaultIndex;
+ this.state = {
+ layout: {x: 0, y: 0, width: 0, height: 0},
+ index: index,
+ imageInfos: this.initImageInfos(props.images),
+ leftIndex: index - 1,
+ rightIndex: index + 1,
+ leftTranslateX: new Animated.Value(0),
+ rightTranslateX: new Animated.Value(0),
+ directionFactor: 0, //-1 prev 1 next
+ };
+ }
+
+ componentDidMount() {
+ this.preloadImage(this.state.index);
+ }
+
+ componentWillReceiveProps(nextProps) {
+ let {imageInfos, index} = this.state;
+ if ((nextProps.index || nextProps.index === 0) && nextProps.index != this.props.index) {
+ index = nextProps.index;
+ }
+ if (nextProps.images.length != this.props.images.length) {
+ imageInfos = this.initImageInfos(nextProps.images);
+ }
+ this.preloadImage(index);
+ this.setState({index, imageInfos}, () => this.checkLeftRight());
+ }
+
+ initImageInfos(images) {
+ let layouts = [];
+ for (let i = 0; i < images.length; ++i) {
+ layouts.push({
+ loadStatus: 0, //0: none 1: thumb loaded 2: image loaded (-1: load failure)
+ width: 1,
+ height: 1,
+ translateY: 0,
+ scale: 1,
+ });
+ }
+ return layouts;
+ }
+
+ getImageSize(source, success, failure) {
+ if (typeof source === 'number') {
+ let {width, height} = resolveAssetSource(source);
+ success && success(width, height);
+ } else if (source && typeof source === 'object' && source.uri) {
+ Image.getSize(source.uri,
+ (width, height) => success && success(width, height),
+ (error) => failure && failure(error)
+ );
+ } else {
+ failure && failure('source error');
+ }
+ }
+
+ getImageSizeInfo(index) {
+ let {layout} = this.state;
+ let imageInfo = this.state.imageInfos[index];
+
+ let initWidth = layout.width;
+ let initHeight = imageInfo.height * initWidth / imageInfo.width;
+ if (initHeight > layout.height) {
+ initHeight = layout.height;
+ initWidth = imageInfo.width * initHeight / imageInfo.height;
+ }
+
+ return {
+ width: imageInfo.width,
+ height: imageInfo.height,
+ initWidth,
+ initHeight,
+ scaleWidth: initWidth * imageInfo.scale,
+ scaleHeight: initHeight * imageInfo.scale,
+ }
+ }
+
+ loadImage(index) {
+ let {images, thumbs, onWillLoadImage, onLoadImageSuccess, onLoadImageFailure} = this.props;
+ let {imageInfos} = this.state;
+ let imageInfo = imageInfos[index];
+
+ if (index < 0 || index >= images.length) {
+ return;
+ }
+
+ if (imageInfo.loadStatus === 0 && thumbs instanceof Array && thumbs.length > index) {
+ this.getImageSize(thumbs[index], (width, height) => {
+ if (imageInfo.loadStatus === 0) {
+ imageInfo.loadStatus = 1;
+ imageInfo.width = width;
+ imageInfo.height = height;
+ this.setState({imageInfos});
+ }
+ });
+ }
+ if (imageInfo.loadStatus !== 2) {
+ onWillLoadImage && onWillLoadImage(index);
+ this.getImageSize(images[index], (width, height) => {
+ imageInfo.loadStatus = 2;
+ imageInfo.width = width;
+ imageInfo.height = height;
+ this.setState({imageInfos});
+ onLoadImageSuccess && onLoadImageSuccess(index, width, height);
+ }, error => {
+ onLoadImageFailure && onLoadImageFailure(index, error);
+ });
+ }
+ }
+
+ preloadImage(index) {
+ this.loadImage(index);
+ this.loadImage(index - 1);
+ this.loadImage(index + 1);
+ }
+
+ checkStopScroll(noDelay) {
+ if (!this.scrollParam) return;
+
+ let {onChange} = this.props;
+ let {newIndex, directionFactor, lrAnimated, lrValue, tvValue} = this.scrollParam;
+ this.scrollParam = null;
+
+ lrAnimated.stopAnimation();
+ this.refs.transformView.state.translateX.stopAnimation();
+ lrAnimated.setValue(lrValue);
+ this.refs.transformView.state.translateX.setValue(tvValue);
+
+ this.preloadImage(newIndex);
+ this.setState({index: newIndex, directionFactor}, () => {
+ onChange && onChange(newIndex, index);
+ noDelay ? this.checkLeftRight() : setTimeout(() => this.checkLeftRight(), 200);
+ });
+ }
+
+ scrollToImage(newIndex) {
+ let {images, space} = this.props;
+ let {index} = this.state;
+ let {width} = this.state.layout;
+
+ if (newIndex < 0 || newIndex >= images.length || newIndex == index) {
+ return;
+ }
+
+ let directionFactor = newIndex < index ? -1 : 1;
+ let lrAnimated = newIndex < index ? this.state.leftTranslateX : this.state.rightTranslateX;
+ let lrValue = -(width + space) * directionFactor;
+ let distance = lrValue - lrAnimated._value;
+ let tvValue = this.refs.transformView.state.translateX._value + distance;
+
+ this.scrollParam = {
+ newIndex,
+ directionFactor,
+ lrAnimated,
+ lrValue,
+ tvValue,
+ };
+
+ Animated.parallel([
+ Animated.spring(lrAnimated, {
+ toValue: lrValue,
+ friction: 9,
+ }),
+ Animated.spring(this.refs.transformView.state.translateX, {
+ toValue: tvValue,
+ friction: 9,
+ }),
+ ]).start(e => this.checkStopScroll(false));
+ // let the animation stop faster
+ lrAnimated.addListener(e => {
+ if (Math.abs(e.value - lrValue) < 3) {
+ lrAnimated.stopAnimation();
+ this.refs.transformView.state.translateX.stopAnimation();
+ lrAnimated.removeAllListeners();
+ }
+ });
+
+ }
+
+ // for CarouselControl, no scroll
+ scrollToPage(newIndex) {
+ let {images, onChange} = this.props;
+ let {index} = this.state;
+
+ if (newIndex < 0 || newIndex >= images.length || newIndex == index) {
+ return;
+ }
+
+ let directionFactor = newIndex < index ? -1 : 1;
+ this.preloadImage(newIndex);
+ this.setState({index: newIndex, directionFactor}, () => {
+ onChange && onChange(newIndex, index);
+ setTimeout(() => this.checkLeftRight(), 200);
+ });
+ }
+
+ checkLeftRight() {
+ let {index, imageInfos, leftIndex, rightIndex, leftTranslateX, rightTranslateX, directionFactor} = this.state;
+
+ if (leftIndex != index - 1 || rightIndex != index + 1) {
+ let {width} = this.state.layout;
+ let {scaleWidth} = this.getImageSizeInfo(index);
+ let imageInfo = imageInfos[index];
+ let tx = scaleWidth > width ? (scaleWidth - width) / 2 * directionFactor : 0;
+ this.refs.transformView.state.translateX.setValue(tx);
+ this.refs.transformView.state.translateY.setValue(imageInfo.translateY);
+ this.refs.transformView.state.scale.setValue(imageInfo.scale);
+ this.saveScale = imageInfo.scale;
+
+ leftTranslateX.setValue(0);
+ rightTranslateX.setValue(0);
+ this.setState({leftIndex: index - 1, rightIndex: index + 1});
+ }
+ }
+
+ onTransforming(translateX, translateY, scale) {
+ let saveScale = this.saveScale;
+ this.saveScale = scale;
+ if (scale < 1 || (saveScale && scale < saveScale)) {
+ return;
+ }
+
+ let {x, y, width, height} = this.refs.transformView.contentLayout;
+ let ltx = translateX, rtx = translateX;
+ if (width > this.state.layout.width) {
+ ltx = x;
+ rtx = x + (width - this.state.layout.width);
+ }
+
+ this.state.leftTranslateX.setValue(ltx);
+ this.state.rightTranslateX.setValue(rtx);
+ }
+
+ onDidTransform(translateX, translateY, scale) {
+ this.saveScale = scale;
+ let {index, imageInfos} = this.state;
+ imageInfos[index].translateY = translateY;
+ imageInfos[index].scale = scale;
+ this.setState({imageInfos});
+ }
+
+ onWillMagnetic(translateX, translateY, scale, newX, newY, newScale) {
+ let {images, space} = this.props;
+ let {index} = this.state;
+
+ let {x, y, width, height} = this.refs.transformView.contentLayout;
+ let ltx = translateX, rtx = translateX;
+ if (width > this.state.layout.width) {
+ ltx = x;
+ rtx = x + (width - this.state.layout.width);
+ }
+ let triggerWidth = this.state.layout.width / 3;
+
+ if (scale < 1) {
+ return true;
+ } else if ((ltx < triggerWidth && rtx > -triggerWidth)
+ || (ltx >= triggerWidth && index === 0)
+ || (rtx <= -triggerWidth && index === images.length - 1)) {
+ // scroll to current image
+ let distance = newX - translateX;
+ let newltx = ltx + distance;
+ let newrtx = rtx + distance;
+ Animated.parallel([
+ Animated.spring(this.state.leftTranslateX, {
+ toValue: newltx,
+ friction: 9,
+ }),
+ Animated.spring(this.state.rightTranslateX, {
+ toValue: newrtx,
+ friction: 9,
+ }),
+ ]).start();
+ return true;
+ }
+
+ this.scrollToImage(ltx >= triggerWidth ? index - 1 : index + 1);
+
+ return false;
+ }
+
+ renderLeftImage() {
+ let {images, thumbs, space} = this.props;
+ let {leftIndex, imageInfos, leftTranslateX} = this.state;
+ if (leftIndex < 0 || leftIndex >= images.length) return null;
+
+ let {loadStatus, translateY, scale} = imageInfos[leftIndex];
+ let {width, height} = this.state.layout;
+ let {scaleWidth} = this.getImageSizeInfo(leftIndex);
+ let cy = height / 2;
+ let top = -cy * scale + translateY + cy;
+ let viewStyle = {
+ position: 'absolute',
+ left: -(width + space),
+ top: 0,
+ width,
+ height,
+ };
+ let imageStyle = {
+ position: 'absolute',
+ top: top,
+ right: 0,
+ width: scaleWidth > width ? scaleWidth : width,
+ height: height * scale,
+ transform: [{translateX: leftTranslateX}],
+ };
+ let imageSource;
+ switch (loadStatus) {
+ case 1: imageSource = thumbs[leftIndex]; break;
+ case 2: imageSource = images[leftIndex]; break;
+ default: imageSource = null;
+ }
+
+ return (
+
+
+
+ );
+ }
+
+ renderRightImage() {
+ let {images, thumbs, space} = this.props;
+ let {rightIndex, imageInfos, rightTranslateX} = this.state;
+ if (rightIndex < 0 || rightIndex >= images.length) return null;
+
+ let {loadStatus, translateY, scale} = imageInfos[rightIndex];
+ let {width, height} = this.state.layout;
+ let {scaleWidth} = this.getImageSizeInfo(rightIndex);
+ let cy = height / 2;
+ let top = -cy * scale + translateY + cy;
+ let viewStyle = {
+ position: 'absolute',
+ left: width + space,
+ top: 0,
+ width,
+ height,
+ };
+ let imageStyle = {
+ position: 'absolute',
+ top: top,
+ left: 0,
+ width: scaleWidth > width ? scaleWidth : width,
+ height: height * scale,
+ transform: [{translateX: rightTranslateX}],
+ };
+ let imageSource;
+ switch (loadStatus) {
+ case 1: imageSource = thumbs[rightIndex]; break;
+ case 2: imageSource = images[rightIndex]; break;
+ default: imageSource = null;
+ }
+
+ return (
+
+
+
+ );
+ }
+
+ renderImage() {
+ let {images, thumbs, space, maxScale, onPress, onLongPress} = this.props;
+ let {index, imageInfos} = this.state;
+ let {loadStatus, width, height} = imageInfos[index];
+
+ let {initWidth, initHeight} = this.getImageSizeInfo(index);
+ let imageSource;
+ switch (loadStatus) {
+ case 1: imageSource = thumbs[index]; break;
+ case 2: imageSource = images[index]; break;
+ default: imageSource = null;
+ }
+ return (
+ this.checkStopScroll(true)}
+ onTransforming={(translateX, translateY, scale) => this.onTransforming(translateX, translateY, scale)}
+ onDidTransform={(translateX, translateY, scale) => this.onDidTransform(translateX, translateY, scale)}
+ onWillMagnetic={(translateX, translateY, scale, newX, newY, newScale) => this.onWillMagnetic(translateX, translateY, scale, newX, newY, newScale)}
+ onPress={e => onPress && onPress(index, e)}
+ onLongPress={e => onLongPress && onLongPress(index, e)}
+ ref='transformView'
+ >
+ this.checkLeftRight()}
+ key={index}
+ />
+
+ );
+ }
+
+ render() {
+ let {images, thumbs, defaultIndex, index, maxScale, space, control, children, onLayout, ...others} = this.props;
+
+ if (React.isValidElement(control)) {
+ control = React.cloneElement(control, {index: this.state.index, total: images.length, carousel: this});
+ } else if (control) {
+ control =
+ }
+
+ return (
+ {
+ this.setState({layout: e.nativeEvent.layout});
+ onLayout && onLayout(e);
+ }}
+ {...others}
+ >
+ {this.renderImage()}
+ {this.renderLeftImage()}
+ {this.renderRightImage()}
+ {control}
+
+ );
+ }
+
+}
diff --git a/components/TransformView/TransformView.js b/components/TransformView/TransformView.js
index a4346b5..6b16707 100644
--- a/components/TransformView/TransformView.js
+++ b/components/TransformView/TransformView.js
@@ -16,22 +16,28 @@ export default class TransformView extends Component {
maxScale: PropTypes.number,
minScale: PropTypes.number,
magnetic: PropTypes.bool,
+ tension: PropTypes.bool,
onWillTransform: PropTypes.func, //(translateX, translateY, scale)
onTransforming: PropTypes.func, //(translateX, translateY, scale)
onDidTransform: PropTypes.func, //(translateX, translateY, scale)
+ onWillMagnetic: PropTypes.func, //(translateX, translateY, scale, newX, newY, newScale), return ture or false
+ onDidMagnetic: PropTypes.func, //(translateX, translateY, scale)
onPress: PropTypes.func, //(event)
+ onLongPress: PropTypes.func, //(event)
};
static defaultProps = {
...View.defaultProps,
magnetic: true,
+ tension: true,
};
constructor(props) {
super(props);
this.createPanResponder();
this.prevTouches = [];
- this.initLayout = {x: 0, y: 0, width: 0, height: 0};
+ this.viewLayout = {x: 0, y: 0, width: 0, height: 0};
+ this.initContentLayout = {x: 0, y: 0, width: 0, height: 0};
this.state = {
translateX: new Animated.Value(0),
translateY: new Animated.Value(0),
@@ -39,41 +45,34 @@ export default class TransformView extends Component {
};
}
- get layout() {
+ get contentLayout() {
let {translateX, translateY, scale} = this.state;
- let originX = this.initLayout.x + this.initLayout.width / 2;
- let originY = this.initLayout.y + this.initLayout.height / 2;
+ let originX = this.initContentLayout.x + this.initContentLayout.width / 2;
+ let originY = this.initContentLayout.y + this.initContentLayout.height / 2;
let scaleOriginX = originX + translateX._value;
let scaleOriginY = originY + translateY._value;
- let scaleWidth = this.initLayout.width * scale._value;
- let scaleHeight = this.initLayout.height * scale._value;
+ let scaleWidth = this.initContentLayout.width * scale._value;
+ let scaleHeight = this.initContentLayout.height * scale._value;
let scaleX = scaleOriginX - scaleWidth / 2;
let scaleY = scaleOriginY - scaleHeight / 2;
- let layout = {x: scaleX, y: scaleY, width: scaleWidth, height: scaleHeight};
- return layout;
+ let contentLayout = {x: scaleX, y: scaleY, width: scaleWidth, height: scaleHeight};
+ return contentLayout;
}
- restoreLayout(animated) {
- let {translateX, translateY, scale} = this.state;
- if (animated) {
- Animated.parallel([
- Animated.spring(translateX, {
- toValue: 0,
- friction: 7,
- }),
- Animated.spring(translateY, {
- toValue: 0,
- friction: 7,
- }),
- Animated.spring(scale, {
- toValue: 1,
- friction: 7,
- }),
- ]).start();
- } else {
- translateX.setValue(0);
- translateY.setValue(0);
- scale.setValue(1);
+ setupLongPressTimer(e) {
+ let {onLongPress} = this.props;
+ if (!onLongPress) return;
+ this.removeLongPressTimer();
+ this.longPressTimer = setTimeout(() => {
+ this.longPressTimer = null;
+ onLongPress && onLongPress(e);
+ }, 500);
+ }
+
+ removeLongPressTimer() {
+ if (this.longPressTimer) {
+ clearTimeout(this.longPressTimer);
+ this.longPressTimer = null;
}
}
@@ -93,12 +92,11 @@ export default class TransformView extends Component {
}
onPanResponderGrant(e, gestureState) {
- //let initLayout relative to screen
- this.refs.view && this.refs.view.measureInWindow((x, y, width, height) => {
- Object.assign(this.initLayout, {x, y, width, height});
- });
-
+ this.setupLongPressTimer(e);
this.touchMoved = false;
+ this.lockDirection = 'none';
+ this.dxSum = 0;
+ this.dySum = 0;
this.touchTime = new Date();
this.prevTouches = e.nativeEvent.touches;
let {onWillTransform} = this.props;
@@ -107,25 +105,51 @@ export default class TransformView extends Component {
}
onPanResponderMove(e, gestureState) {
+ this.removeLongPressTimer();
this.touchMoved = true;
this.handleTouches(e.nativeEvent.touches, (dx, dy, scaleRate) => {
- let {magnetic, onTransforming} = this.props;
+ let {tension, onTransforming} = this.props;
let {translateX, translateY, scale} = this.state;
- let {x, y, width, height} = this.layout;
- if (x > this.initLayout.x) dx /= 3;
- else if ((x + width) < (this.initLayout.x + this.initLayout.width)) dx /= 3;
- if (y > this.initLayout.y) dy /= 3;
- else if ((y + height) < (this.initLayout.y + this.initLayout.height)) dy /= 3;
+ let {x, y, width, height} = this.contentLayout;
+ if (tension) {
+ if (x > this.initContentLayout.x) dx /= 3;
+ else if ((x + width) < (this.initContentLayout.x + this.initContentLayout.width)) dx /= 3;
+ if (y > this.initContentLayout.y) dy /= 3;
+ else if ((y + height) < (this.initContentLayout.y + this.initContentLayout.height)) dy /= 3;
+ }
+ this.dxSum += dx;
+ this.dySum += dy;
+ if (e.nativeEvent.touches.length == 1 && this.lockDirection === 'none') {
+ let adx = Math.abs(this.dxSum), ady = Math.abs(this.dySum);
+ if (adx > ady && height <= this.viewLayout.height) {
+ this.lockDirection = 'y';
+ } else if (adx < ady && width <= this.viewLayout.width) {
+ this.lockDirection = 'x';
+ }
+ }
+
+ switch(this.lockDirection) {
+ case 'x':
+ translateX.setValue(0);
+ translateY.setValue(translateY._value + dy);
+ break;
+ case 'y':
+ translateX.setValue(translateX._value + dx);
+ translateY.setValue(0);
+ break;
+ default:
+ translateX.setValue(translateX._value + dx);
+ translateY.setValue(translateY._value + dy);
+ scale.setValue(scale._value * scaleRate);
+ }
- translateX.setValue(translateX._value + dx);
- translateY.setValue(translateY._value + dy);
- scale.setValue(scale._value * scaleRate);
onTransforming && onTransforming(translateX._value, translateY._value, scale._value);
});
}
onPanResponderRelease(e, gestureState) {
+ this.removeLongPressTimer();
this.prevTouches = [];
this.handleRelease();
let {onDidTransform, onPress} = this.props;
@@ -133,7 +157,10 @@ export default class TransformView extends Component {
onDidTransform && onDidTransform(translateX._value, translateY._value, scale._value);
let now = new Date();
if (!this.touchTime) this.touchTime = now;
- !this.touchMoved && now.getTime() - this.touchTime.getTime() < 500 && onPress && onPress(e);
+ if (!this.touchMoved) {
+ let duration = now.getTime() - this.touchTime.getTime();
+ if (duration < 500) onPress && onPress(e);
+ }
}
handleTouches(touches, onHandleCompleted) {
@@ -183,7 +210,7 @@ export default class TransformView extends Component {
let scalePointX = (prevTouches[1].pageX + prevTouches[0].pageX) / 2;
let scalePointY = (prevTouches[1].pageY + prevTouches[0].pageY) / 2;
- let {x, y, width, height} = this.layout;
+ let {x, y, width, height} = this.contentLayout;
//view center point position
let viewCenterX = x + width / 2;
let viewCenterY = y + height / 2;
@@ -202,25 +229,29 @@ export default class TransformView extends Component {
}
handleRelease() {
- let {magnetic, maxScale, minScale} = this.props;
+ let {magnetic, maxScale, minScale, onDidTransform, onWillMagnetic, onDidMagnetic} = this.props;
let {translateX, translateY, scale} = this.state;
let newX = null, newY = null, newScale = null;
if (magnetic) {
- let {x, y, width, height} = this.layout;
- if (width < this.initLayout.width || height < this.initLayout.height) {
+ let {x, y, width, height} = this.contentLayout;
+ if (width < this.initContentLayout.width || height < this.initContentLayout.height) {
newX = 0;
newY = 0;
newScale = 1;
} else {
- if (x > this.initLayout.x) {
- newX = translateX._value - (x - this.initLayout.x);
- } else if ((x + width) < (this.initLayout.x + this.initLayout.width)) {
- newX = translateX._value + ((this.initLayout.x + this.initLayout.width) - (x + width));
+ if (width < this.viewLayout.width) {
+ newX = 0;
+ } else if (x > this.viewLayout.x) {
+ newX = translateX._value - (x - this.viewLayout.x);
+ } else if ((x + width) < (this.viewLayout.x + this.viewLayout.width)) {
+ newX = translateX._value + ((this.viewLayout.x + this.viewLayout.width) - (x + width));
}
- if (y > this.initLayout.y) {
- newY = translateY._value - (y - this.initLayout.y);
- } else if ((y + height) < (this.initLayout.y + this.initLayout.height)) {
- newY = translateY._value + ((this.initLayout.y + this.initLayout.height) - (y + height));
+ if (height < this.viewLayout.height) {
+ newY = 0;
+ } else if (y > this.viewLayout.y) {
+ newY = translateY._value - (y - this.viewLayout.y);
+ } else if ((y + height) < (this.viewLayout.y + this.viewLayout.height)) {
+ newY = translateY._value + ((this.viewLayout.y + this.viewLayout.height) - (y + height));
}
}
}
@@ -233,37 +264,54 @@ export default class TransformView extends Component {
newX !== null && animates.push(
Animated.spring(translateX, {
toValue: newX,
- friction: 7,
+ friction: 9,
})
);
newY !== null && animates.push(
Animated.spring(translateY, {
toValue: newY,
- friction: 7,
+ friction: 9,
})
);
newScale !== null && animates.push(
Animated.spring(scale, {
toValue: newScale,
- friction: 7,
+ friction: 9,
})
);
- animates.length > 0 && Animated.parallel(animates).start();
+ if (animates.length > 0) {
+ if (newX === null) newX = translateX._value;
+ if (newY === null) newY = translateY._value;
+ if (newScale === null) newScale = scale._value;
+ let canDoMagnetic = !onWillMagnetic || onWillMagnetic(
+ translateX._value,
+ translateY._value,
+ scale._value,
+ newX,
+ newY,
+ newScale,
+ );
+ canDoMagnetic && Animated.parallel(animates).start(e => {
+ translateX.setValue(newX);
+ translateY.setValue(newY);
+ scale.setValue(newScale);
+ onDidTransform && onDidTransform(newX, newY, newScale);
+ onDidMagnetic && onDidMagnetic(newX, newY, newScale);
+ });
+ }
}
buildProps() {
let {style, containerStyle, ...others} = this.props;
let {translateX, translateY, scale} = this.state;
- style = StyleSheet.flatten([{overflow: 'hidden'}].concat(style));
- let {flexDirection, alignItems, justifyContent, ...styleOthers} = style;
- style = {...styleOthers};
- containerStyle = [{
- flexDirection,
- alignItems,
- justifyContent,
- }].concat(containerStyle).concat({
- flexGrow: 1,
+ style = StyleSheet.flatten([{
+ flex: 1,
+ alignItems: 'center',
+ justifyContent: 'center',
+ overflow: 'hidden',
+ }].concat(style));
+ containerStyle = [].concat(containerStyle).concat({
transform: [{translateX: translateX}, {translateY: translateY}, {scale: scale}],
});
@@ -273,17 +321,23 @@ export default class TransformView extends Component {
render() {
this.buildProps();
- let {containerStyle, children,...others} = this.props;
+ let {containerStyle, children, onLayout, ...others} = this.props;
return (
-
+ {
+ this.viewLayout = e.nativeEvent.layout;
+ onLayout && onLayout(e);
+ }}
+ ref='view'
+ {...this.panResponder.panHandlers}
+ >
{
- this.initLayout = e.nativeEvent.layout;
- this.restoreLayout(false);
+ this.initContentLayout = e.nativeEvent.layout;
}}
- {...this.panResponder.panHandlers}
>
{children}
diff --git a/docs/cn/AlbumView.md b/docs/cn/AlbumView.md
new file mode 100644
index 0000000..db31c60
--- /dev/null
+++ b/docs/cn/AlbumView.md
@@ -0,0 +1,60 @@
+# `` 相册视图
+AlbumView 组件定义一个相册视图, 支持多图左右切换显示,支持双指按捏缩放、单指触摸移动手势, 使用纯 JS 实现, 同时支持 Android 和 iOS。
+
+## Props
+| Prop | Type | Default | Note |
+|---|---|---|---|
+| [View props...](https://facebook.github.io/react-native/docs/view.html) | | | AlbumView 组件继承 View 组件的全部属性。
+| images | array | | 相册图片数组,必填,数组元素为 Image.source 。
+| thumbs | array | | 相册缩略图数组,可空,数组元素为 Image.source 。
+| defaultIndex | number | 0 | 默认显示图片索引。
+| index | number | | 显示图片索引,设置此属性需要监听 onChange 事件并自行维护状态。
+| maxScale | number | 3 | 最大缩放倍数。
+| space | number | 20 | 相册图片间隔空间。
+| control | bool
element | false | 页面控制器, 为 true 时显示默认页面控制器, 也可以传入自定义的页面控制器, 建议使用 Carousel.Control 组件。
+
+## Events
+| Event Name | Returns | Notes |
+|---|---|---|
+| [View events...](https://facebook.github.io/react-native/docs/view.html) | | AlbumView 组件继承 View 组件的全部事件。
+| onChange | index, oldIndex | 改变当前页面时调用, index 为改变后页面索引值, oldIndex 为改变前页面索引值。
+| onPress | index, event | 单击事件, 触摸结束时调用。
+| onLongPress | index, event | 长按事件, 按压组件超过 500ms 时调用。
+| onWillLoadImage | index | 加载图片前调用。
+| onLoadImageSuccess | index, width, height | 加载图片成功时调用。
+| onLoadImageFailure | index, error | 加载图片失败时调用。
+
+
+
+## Example
+简单用法
+```
+
+```
+
+## Screenshots
+![](https://github.com/rilyu/teaset/blob/master/screenshots/14a-AlbumView1.png?raw=true) ![](https://github.com/rilyu/teaset/blob/master/screenshots/14a-AlbumView2.png?raw=true)
diff --git a/docs/cn/README.md b/docs/cn/README.md
index cdc1679..e931517 100644
--- a/docs/cn/README.md
+++ b/docs/cn/README.md
@@ -106,6 +106,8 @@ react-native run-android
[`` 可变视图](./TransformView.md)
+[`` 相册视图](./AlbumView.md)
+
## 浮层
[`Overlay{}` 浮层](./Overlay.md)
diff --git a/docs/cn/TransformView.md b/docs/cn/TransformView.md
index d7ab3cc..f603466 100644
--- a/docs/cn/TransformView.md
+++ b/docs/cn/TransformView.md
@@ -8,7 +8,8 @@ TransformView 组件定义一个可变视图, 支持双指按捏缩放、单指
| containerStyle | View.style | | 内部容器样式。
| maxScale | number | | 最大缩放倍数。
| minScale | number | | 最小缩放倍数。
-| magnetic | number | true | 磁性边框, 当缩放后尺寸小于视图尺寸时自动放大到视图大小。
+| magnetic | bool | true | 磁性边框, 当缩放后尺寸小于视图尺寸时自动放大到视图大小。
+| tension | bool | true | 拉拽阻力, 当图片边缘在视图内时继续拉拽有阻力效果。
## Events
| Event Name | Returns | Notes |
@@ -17,7 +18,10 @@ TransformView 组件定义一个可变视图, 支持双指按捏缩放、单指
| onWillTransform | translateX, translateY, scale | Transform 开始, 三个参数分别为 x 坐标、y 坐标、缩放倍数。
| onTransforming | translateX, translateY, scale | Transform 进行中, 三个参数分别为 x 坐标、y 坐标、缩放倍数。
| onDidTransform | translateX, translateY, scale | Transform 结束, 三个参数分别为 x 坐标、y 坐标、缩放倍数。
+| onWillMagnetic | translateX, translateY, scale, newX, newY, newScale | 磁性边框效果开始前调用,返回 true 允许磁性边框效果,否则阻止磁性边框效果效果, magnetic = true 时有效。
+| onDidMagnetic | translateX, translateY, scale | 磁性边框效果结束时调用, 三个参数分别为 x 坐标、y 坐标、缩放倍数。
| onPress | event | 单击事件, 触摸结束时调用, 与 TouchableOpacity.onPress 一致。
+| onLongPress | event | 长按事件, 按压组件超过 500ms 时调用, 与 TouchableOpacity.onLongPress 一致。