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 一致。