1.在index.android.js下书写
/** * Created by TinySymphony on 2017-03-23. */ import React, {Component} from 'react'; import { StyleSheet, AppRegistry, View, Text, Easing, ScrollView } from 'react-native'; import ZoomImage from './ZoomImage'; export default class App extends Component { constructor( props) { super(props); this.text = ''; } render() { return ( <ScrollView > <View style ={styles.content} > < Text style ={styles.txt} >Zoom Image Examples ! Try to click them ~</ Text > <View style ={styles.imgItem} > <ZoomImage source ={{uri : 'https://avatars2.githubusercontent.com/u/7685233?v=3&s=460'}} imgStyle ={{width : 220, height : 220}} style ={styles.img} /> </View > <View style ={styles.imgItem} > <ZoomImage source ={{uri : 'https://ooo.0o0.ooo/2017/03/31/58de0e9b287f6.jpg'}} imgStyle ={{width : 250, height : 230}} style ={styles.img} enableScaling ={ true} /> </View > <View style ={styles.imgItem} > <ZoomImage source ={{uri : 'https://ooo.0o0.ooo/2017/03/31/58de0e9b28328.jpg'}} imgStyle ={{width : 250, height : 240}} style ={styles.img} easingFunc ={Easing.bounce} /> </View > </View > </ScrollView > ); } } const styles = StyleSheet. create({ content : { justifyContent : 'center', alignItems : 'center' }, txt : { fontSize : 16, marginTop : 50, color : '#333' }, img : { borderWidth : 3, borderColor : '#45b7d5' }, imgItem : { justifyContent : 'center', alignItems : 'center', margin : 20 } }); AppRegistry. registerComponent( 'App', () => App); // export default App; // import { AppRegistry } from 'react-native'; // import App from './App'; // AppRegistry.registerComponent('Example', (): App => App);2.ZoomImage.js文件
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, ( w, h) => { 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), ( x, y, w, h, px, py) => { 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 : ( evt, gestureState) => this.props.startCapture, onMoveShouldSetPanResponder : this._onMoveShouldSetPanResponder. bind(this), onMoveShouldSetPanResponderCapture : ( evt, gestureState) => this.props.moveCapture, onPanResponderTerminationRequest : ( evt, gestureState) => true, onPanResponderGrant : this._handlePanResponderGrant. bind(this), onPanResponderMove : this._handlePanResponderMove. bind(this), onPanResponderRelease : this._handlePanResponderEnd. bind(this), onPanResponderTerminate : this._handlePanResponderEnd. bind(this), onShouldBlockNativeResponder : ( evt, gestureState) => true }); } _onStartShouldSetPanResponder ( evt, gestureState) { // set responder for tapping when the drawer is open // TODO: tap close if (this._inAnimation) return; return false; } _onMoveShouldSetPanResponder ( evt, gestureState) { // custom pan responder condition function if (this._inAnimation) return; if (this.props.responderNegotiate && this.props. responderNegotiate(evt, gestureState) === false) return false; if (this. _touchPositionCheck(gestureState)) { return true; } return false; } _handlePanResponderGrant( evt, gestureState) { } _handlePanResponderMove ( evt, gestureState) { const { dy} = gestureState; this. _updateNativeStyles(dy); } _handlePanResponderEnd ( evt, gestureState) { 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 { dx, dy} = gestureState; if ( Math. abs(dy) <= Math. abs(dx)) { return false; } return true; } _closeModal( isDown) { const { easingFunc, onClose} = 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 { duration, easingFunc} = 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;
3.Animation.js
export default function Animation( option) { this.animate = this.animate. bind(this); this.start = this.start. bind(this); this.option = option; } Animation.prototype. animate = function ( now) { const { start, end, duration, onAnimationFrame, onAnimationEnd = () => {}, easingFunc = t => t } = this.option; var currentDuration = now - this.startTime; if (currentDuration >= duration) { onAnimationFrame(end); onAnimationEnd(); return; } let value; if (start > end) { value = start - (start - end) * easingFunc(currentDuration / duration); } else { value = (end - start) * easingFunc(currentDuration / duration) + start; } onAnimationFrame(value); requestAnimationFrame(this.animate); }; Animation.prototype. start = function () { this.startTime = new Date(); this. animate(this.startTime); };
项目原地址https://github.com/Tinysymphony/react-native-zoom-image
