我正在尝试创建一个组件,让您可以使用捏合放大和缩小图像,然后在区域中移动图像。最终目标是显示地图的图像并进行探索。地图将永远是一张图片。
图像包含在我为其设置panResponder的视图中。 panResponder区分夹点和触摸事件并调用相应的函数。
我设法让“缩放”功能以及“四处移动”功能完成工作,但后者对我来说似乎很笨拙。我怀疑我的计算略有偏差,这不是我的强项。
实施它的最佳方法是什么?
以下是整个代码:
import React, {
Component,
} from 'react';
import {
Dimensions,
AppRegistry,
StyleSheet,
Text,
View,
Image,
TouchableOpacity,
PanResponder,
} from 'react-native';
const screenWidth = Dimensions.get('window').width;
const screenHeight = Dimensions.get('window').height;
const img = require('image!cars');
const scaleStep = 0.1;
const DIR_IN = 'IN';
const DIR_OUT = 'OUT';
class reactzoom extends Component {
constructor(props) {
super(props);
this.state = {
zoom: 1,
x: 0,
y: 0,
distance: 0,
isZooming: false,
isMoving: false,
};
}
getDimensionsToFitArea(image, areaDimensions) {
const verticalFactor = areaDimensions.height / image.height;
const horizontalFactor = areaDimensions.width / image.width;
const imageFactor = Math.min(verticalFactor, horizontalFactor);
return {
width: image.width * imageFactor,
height: image.height * imageFactor,
};
}
setZoom(zoom) {
this.setState({
zoom: zoom,
});
}
zoomIn() {
const newZoom = this.state.zoom + scaleStep;
this.setZoom(newZoom);
}
zoomOut() {
let newZoom = this.state.zoom - (scaleStep * 1.5);
if (newZoom < 1) {
newZoom = 1;
this.resetCenter();
}
this.setZoom(newZoom);
}
cancelZoom() {
this.setZoom(1);
this.resetCenter();
}
resetCenter() {
this.setCenter(0, 0);
}
setCenter(x, y) {
let newX = 0;
let newY = 0;
if (this.state.zoom > 1) {
const imgAreaDimensions = this.getImageAreaDimensions();
if (x != 0 && y != 0) {
newX = (imgAreaDimensions.width / 2) - x;
newY = (imgAreaDimensions.height / 2) - y;
}
}
const newState = {
x: newX,
y: newY,
};
this.setState(newState);
}
getImageAreaDimensions() {
return {
width: screenWidth,
height: screenHeight / 2
};
}
processTouch(x, y) {
if (!this.state.isMoving) {
this.setState({
isMoving: true,
initialX: x,
initialY: y,
pathDoneX: 0,
pathDoneY: 0,
});
} else {
const path = calcPath(this.state.initialX, this.state.initialY, x, y);
const newX = this.state.initialX - path.x;
const newY = this.state.initialY - path.y;
this.setCenter(newX, newY);
this.setState({
pathDoneX: this.state.pathDoneX + path.x,
pathDoneY: this.state.pathDoneY + path.y,
});
}
}
processPinch(x1, y1, x2, y2) {
const distance = calcDistance(x1, y1, x2, y2);
const center = calcCenter(x1, y1, x2, y2);
const direction = (distance > this.state.distance) ? DIR_IN : DIR_OUT;
if (!this.state.isZooming) {
if (direction === DIR_IN) {
this.setCenter(center.x, center.y);
}
}
else {
(direction === DIR_IN) ? this.zoomIn() : this.zoomOut();
}
this.setState({
distance: distance,
isZooming: true,
});
}
componentWillMount() {
this._panResponder = PanResponder.create({
oneStartShouldSetPanResponderCapture: () => true,
oneMoveShouldSetPanResponder: () => true,
oneMoveShouldSetPanResponderCapture: () => true,
onPanResponderGrant: () => { },
onPanResponderMove: (evt) => {
const touches = evt.nativeEvent.touches;
if (touches.length === 2) {
this.processPinch(touches[0].pageX, touches[0].pageY,
touches[1].pageX, touches[1].pageY);
} else if (touches.length === 1 && !this.state.isZooming) {
this.processTouch(touches[0].pageX, touches[0].pageY);
}
},
onPanResponderTerminationRequest: () => false,
onPanResponderRelease: () => {
this.setState({
isZooming: false,
isMoving: false,
});
},
onPanResponderTerminate: () => { },
onMoveShouldSetPanResponderCapture: (evt, gestureState) => {
return (Math.abs(gestureState.dx) > 2) || (Math.abs(gestureState.dy) > 2)
},
});
}
render() {
const imgAreaDimensions = this.getImageAreaDimensions();
const imgDimensions = this.getDimensionsToFitArea(img, {
width: imgAreaDimensions.width,
height: imgAreaDimensions.height,
});
return (
<View style={styles.container}>
<View
style={{
borderWidth: 1,
borderColor: '#000000',
width: imgAreaDimensions.width,
height: imgAreaDimensions.height,
}}
{...this._panResponder.panHandlers}
>
<Image
style={{
width: imgDimensions.width,
height: imgDimensions.height,
transform: [
{ translateX: this.state.x },
{ translateY: this.state.y },
{ scaleX: this.state.zoom },
{ scaleY: this.state.zoom }
]
}}
source={img}
/>
</View>
<TouchableOpacity
style={styles.button}
onPress={() => this.cancelZoom() }
>
<Text>~</Text>
</TouchableOpacity>
</View>
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: '#F5FCFF',
},
button: {
borderColor: '#000000',
borderWidth: 1,
padding: 25,
paddingBottom: 5,
paddingTop: 5,
alignSelf: 'stretch',
alignItems: 'center',
margin: 1,
height: 50,
}
});
AppRegistry.registerComponent('reactzoom', () => reactzoom);
function calcPath(x1, y1, x2, y2) {
return {
x: x2 - x1,
y: y2 - y1,
};
}
function calcDistance(x1, y1, x2, y2) {
const dx = Math.abs(x1 - x2);
const dy = Math.abs(y1 - y2);
return Math.sqrt(Math.pow(dx, 2) + Math.pow(dy, 2));
}
function calcCenter(x1, y1, x2, y2) {
function middle(p1, p2) {
return p1 > p2 ? p1 - (p1 - p2) / 2 : p2 - (p2 - p1) / 2;
}
return {
x: middle(x1, x2),
y: middle(y1, y2),
};
}
答案 0 :(得分:1)
这可能是因为您正在使用setState
调用来处理地图的位置和缩放。调用PanResponder
个处理程序的比率太高而无法使用setState
进行处理,因为这会导致render
函数被调用并使React协调算法启动
您可以采取的措施是使用Animated
组件并在所需值上调用setValue
。我略微调整了你的代码(但没有测试它)给你一个正确方向的提示。
class ReactZoom extends Component {
constructor(props) {
super(props);
// these values are not kept in the state
// because we will manipulate these directly
this.zoom = new Animated.Value(1);
this.x = new Animated.Value(0);
this.y = new Animated.Value(0);
...
}
setZoom(zoom) {
// change the zoom immediately
this.zoom.setValue(zoom);
}
setCenter(x, y) {
let newX = 0;
let newY = 0;
if (this.state.zoom > 1) {
const imgAreaDimensions = this.getImageAreaDimensions();
if (x != 0 && y != 0) {
newX = (imgAreaDimensions.width / 2) - x;
newY = (imgAreaDimensions.height / 2) - y;
}
}
// this make the center move directly
this.x.setValue(newX);
this.y.setValue(newY);
}
render() {
...
{/* We make use of an Animated.Image because now we can
manipulate the animated values directly without the need to re-render
*/}
<Animated.Image
style={{
width: imgDimensions.width,
height: imgDimensions.height,
transform: [
{ translateX: this.x },
{ translateY: this.y },
{ scaleX: this.zoom },
{ scaleY: this.zoom }
]
}}
source={img}
/>
...
}
}