1. 程式人生 > >React Native 的圖片點選放大效果的元件使用 react-native-zoom-image

React Native 的圖片點選放大效果的元件使用 react-native-zoom-image

import React, {PropTypes, Component} from 'react'; import {   View,   Text,   Image,   Modal,   Easing,   StyleSheet,   PanResponder,   NativeModules,   findNodeHandle,   Dimensions,   TouchableWithoutFeedback } from 'react-native'; import Animation from './Animation'; const winWidth = Dimensions.get
('window').width; const winHeight = Dimensions.get('window').height; const winRatio = winWidth / winHeight;
const RCTUIManager = NativeModules.UIManager;
class ZoomImage extends Component {   static propTypes = {     startCapture: PropTypes.bool,     moveCapture: PropTypes.bool,     responderNegotiate:
 PropTypes.func,     easingFunc: PropTypes.func,     duration: PropTypes.number,     enableScaling: PropTypes.bool   }   static defaultProps = {     startCapture: false,     moveCapture: false,     duration: 800,     easingFunc: Easing.ease,     enableScaling: false   }   constructor(props) {     super(props);     this.state =
 {       maxSize: {         width: 0,         height: 0       },       isModalVisible: false     };     this.enableModal = false;     this.closeModal = this.closeModal.bind(this);     this.openModal = this.openModal.bind(this);     this.getMaxSizeByRatio = this.getMaxSizeByRatio.bind(this);   }   getMaxSizeByRatio (ratio) {     return {       width: ratio >= winRatio ? winWidth : winWidth / ratio,       height: ratio >= winRatio ? winWidth / ratio : winHeight     };   }   componentDidMount () {     if (this.props.source.uri) {       Image.getSize(this.props.source.uri, (wh=> {         this.setState((state=> {           state.maxSize = this.getMaxSizeByRatio(w / h);           this.enableModal = true;         });       });     } else {       this.setState((state=> {         state.maxSize = this.getMaxSizeByRatio(this.props.imgStyle.width / this.props.imgStyle.height);         this.enableModal = true;       });     }   }   openModal () {     if (!this.refs.view || !this.enableModal) return;     RCTUIManager.measure(findNodeHandle(this.refs.view), (xywhpxpy=> {       this.originPosition = {x, y, w, h, px, py};     });     this.setState({       isModalVisible: true     });   }   closeModal () {     this.setState({       isModalVisible: false     });   }   render () {     return (       <TouchableWithoutFeedback style={this.props.imgStyle}         onPress={this.openModal}         ref="view">         <View style={this.props.style}>           <Image             source={this.props.source}             resizeMode={this.props.resizeMode}             style={this.props.imgStyle}/>           <ImageModal             visible={this.state.isModalVisible}             onClose={this.closeModal}             originPosition={this.originPosition}             size={this.state.maxSize}             minAlpha={this.props.minAlpha}             source={this.props.source}             duration={this.props.duration}             easingFunc={this.props.easingFunc}             enableScaling={this.props.enableScaling}/>         </View>       </TouchableWithoutFeedback>     );   } }
class ImageModal extends Component {   constructor(props) {     super(props);     this._initModalStyle = {       style: {         backgroundColor: 'rgba(0, 0, 0, 1)'       }     };     this._modalStyle = JSON.parse(JSON.stringify(this._initModalStyle));     this._initContentStyle = {       style: {         top: 0,         bottom: 0,         left: 0,         right: 0       }     };     this._contentStyle = JSON.parse(JSON.stringify(this._initContentStyle));     this._initImgSize = {       style: this.props.size     };     this._imgSize = JSON.parse(JSON.stringify(this._initImgSize));     this._inAnimation = false;     this._setNativeProps = this._setNativeProps.bind(this);     this._closeModalByTap = this._closeModalByTap.bind(this);     this._closeModal = this._closeModal.bind(this);     this._rebounce = this._rebounce.bind(this);     this._touchPositionCheck = this._touchPositionCheck.bind(this);     this._updateNativeStyles = this._updateNativeStyles.bind(this);     this._pan = PanResponder.create({       onStartShouldSetPanResponder: this._onStartShouldSetPanResponder.bind(this),       onStartShouldSetPanResponderCapture: (evtgestureState=> this.props.startCapture,       onMoveShouldSetPanResponder: this._onMoveShouldSetPanResponder.bind(this),       onMoveShouldSetPanResponderCapture: (evtgestureState=> this.props.moveCapture,       onPanResponderTerminationRequest: (evtgestureState=> true,       onPanResponderGrant: this._handlePanResponderGrant.bind(this),       onPanResponderMove: this._handlePanResponderMove.bind(this),       onPanResponderRelease: this._handlePanResponderEnd.bind(this),       onPanResponderTerminate: this._handlePanResponderEnd.bind(this),       onShouldBlockNativeResponder: (evtgestureState=> true     });   }   _onStartShouldSetPanResponder (evtgestureState) {     // set responder for tapping when the drawer is open     // TODO: tap close     if (this._inAnimation) return;     return false;   }   _onMoveShouldSetPanResponder (evtgestureState) {     // custom pan responder condition function     if (this._inAnimation) return;     if (this.props.responderNegotiate && this.props.responderNegotiate(evt, gestureState) === falsereturn false;     if (this._touchPositionCheck(gestureState)) {       return true;     }     return false;   }   _handlePanResponderGrant(evtgestureState) {   }   _handlePanResponderMove (evtgestureState) {     const {dy= gestureState;     this._updateNativeStyles(dy);   }   _handlePanResponderEnd (evtgestureState) {     const {dy= gestureState;     if (dy > 0.4 * winHeight) {       this._closeModal(true);     } else if (-dy > 0.4 * winHeight) {       this._closeModal(false);     } else {       this._rebounce();     }   }   _touchPositionCheck(gestureState) {     const {dxdy= gestureState;     if (Math.abs(dy) <= Math.abs(dx)) {       return false;     }     return true;   }   _closeModal(isDown) {     const {easingFunconClose= this.props;     let current = this._contentStyle.style.top;     this._inAnimation = true;     new Animation({       start: current,       end: isDown ? winHeight : -winHeight,       duration: 140,       easingFunc,       onAnimationFrame: (val=> {         this._updateNativeStyles(val);       },       onAnimationEnd: () => {         this._inAnimation = false;         onClose();         this._setNativeProps(true);       }     }).start();   }   _closeModalByTap() {     if (this._inAnimation) {       return false;     }     this._closeModal(true);   }   _rebounce(isDown) {     const {durationeasingFunc= this.props;     let current = this._contentStyle.style.top;     this._inAnimation = true;     new Animation({       start: current,       end: 0,       duration: Math.abs(current / winHeight) * duration,       easingFunc,       onAnimationFrame: (val=> {         this._updateNativeStyles(val);       },       onAnimationEnd: () => {         this._inAnimation = false;       }     }).start();   }   _updateNativeStyles(dy) {     const {       width,       height     } = this.props.size;     // this._contentStyle.style.left = dx;     // this._contentStyle.style.right = -dx;     this._contentStyle.style.top = dy;     this._contentStyle.style.bottom = -dy;     this._modalStyle.style.backgroundColor = `rgba(0, 0, 0, ${1-Math.abs(dy) / winHeight *0.9})`;     if (this.props.enableScaling) {       this._imgSize.style.width = width * (1 - Math.abs(dy / winHeight) * 0.6);       this._imgSize.style.height = height * (1 - Math.abs(dy / winHeight) * 0.6);     } else {       this._imgSize.style.width = width;       this._imgSize.style.height = height;     }     this._setNativeProps();   }   _setNativeProps(isReset) {     if (isReset) {       this._contentStyle = JSON.parse(JSON.stringify(this._initContentStyle));       this._modalStyle = JSON.parse(JSON.stringify(this._initModalStyle));       this._imgSize = JSON.parse(JSON.stringify(this._initImgSize));     }     this.content && this.content.setNativeProps(this._contentStyle);     this.mask && this.mask.setNativeProps(this._modalStyle);     this.img && this.img.setNativeProps(this._imgSize);   }   componentDidUpdate () {     new Animation({       start: 0,       end: 1,       duration: 100,       easingFunc: Easing.ease,       onAnimationFrame: (val=> {         this.mask && this.mask.setNativeProps({style: {           opacity: val         }});       },       onAnimationEnd: () => {         this._inAnimation = false;       }     }).start();   }   render () {     const {       visible,       onClose,       source,       size  // origin size of the image     } = this.props;     if (visible) { this._inAnimation = true; }     this._initImgSize.style = size;     return (       <Modal         visible={visible}         transparent={true}         onRequestClose={onClose}>         <View style={styles.mask} ref={mask => {this.mask = mask;}} {...this._pan.panHandlers}>           <TouchableWithoutFeedback             ref={ref => {this.imgContainer = ref;}}             onPress={this._closeModalByTap}>             <View               ref={ref => {this.content = ref;}}               style={styles.content}>               <Image ref={img => {this.img = img;}} source={source} style={[size, styles.img]}/>             </View>           </TouchableWithoutFeedback>         </View>       </Modal>     );   } }
const styles = StyleSheet.create({   mask: {     position: 'absolute',     right: 0,     left: 0,     top: 0,     bottom: 0,     backgroundColor: 'rgba(0, 0, 0, 1)',     opacity: 0   },   content: {     position: 'absolute',     right: 0,     left: 0,     top: 0,     bottom: 0,     justifyContent: 'center',     alignItems: 'center',     backgroundColor: 'transparent'   },   toucharea: {     flex: 1,     justifyContent: 'center',     alignItems: 'center',     alignSelf: 'stretch'   },   modalText: {     color: '#fff'   },   img: {   } });
ZoomImage.ImageModal = ImageModal;
export default ZoomImage;