2016-09-23 9 views
0

ピンチで画像をズームイン/ズームアウトし、その画像をある区域の周りに移動させるコンポーネントを作成しようとしています。最終目標は地図の画像を表示し、それを探索することです。マップは常に画像になります。ビュー内で画像を移動する - リアクションネイティブ

イメージは、私が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

これはまったく役に立ちませんが、私はこのチュートリアルを見ても画像を見てみると役に立つかもしれませんhttp://mindthecode.com/getting-started-with-the-panresponder -in-react-native/ – cjmling

答えて

1

それはおそらくあなたが場所とマップのズームを処理するためにsetState呼び出しを使用しているという事実によるものです。 PanResponderのハンドラが呼び出される割合は、単にそれが呼び出されるrender機能を引き起こし、中に和解アルゴリズムキックに反応作るためsetStateを使用して処理するには高すぎる。

あなたはこれを作ることです修正するために何ができますか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} 
     /> 
    ... 
    } 

} 
+0

あなたの答えをありがとう、私はあなたが私に言ったものを実装しようとしています。私はズームの値を増やす方法を理解できません。私はこれを行うとき:this.setZoom(this.zoom + 0.1);私はこのエラーを受け取りました: "scaleX"のキーを持つTransformは数値でなければなりません:{"scaleX": "[object Object] 0.1"}。値は加算されずに連結されているようです。私は間違って何をしていますか? – vincenth

+0

this.zoomは数字ではないので、数値を追加すると文字列の変換が行われます。たぶんあなたは追加のフィールドにzoomvalueを格納する必要がありますので、簡単な増分を行うことができます – Thomas

+0

ありがとうございます。 Animated.Valueをインクリメントする方法はありませんか、それともインクリメントすることができるように数値を取得し、それをsetValueに渡しますか? – vincenth

関連する問題