在视图中移动图像 - React-native

时间:2016-09-23 10:29:12

标签: react-native

我正在尝试创建一个组件,让您可以使用捏合放大和缩小图像,然后在区域中移动图像。最终目标是显示地图的图像并进行探索。地图将永远是一张图片。

图像包含在我为其设置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),
  };
}

1 个答案:

答案 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}
        />
     ...
  }

}