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);
};