2016-04-02 19 views
5

Ich versuche, ein Bild in meiner React Native App (Android) anzuzeigen, und ich möchte Benutzern die Möglichkeit geben, das Bild hinein und heraus zu zoomen. Dies erfordert auch, dass das Bild scrollbar ist, sobald es herangezoomt wurde.Scrollbares Bild mit Pinch-to-Zoom

Wie würde ich darüber gehen?

Ich habe versucht, ScrollView verwenden, um ein größeres Bild im Inneren anzuzeigen, aber auf Android kann es entweder vertikal oder horizontal, nicht beide Wege scrollen. Auch wenn das funktioniert, gibt es ein Problem, pinch-to-zoom arbeiten zu lassen.

Soweit ich verstehe, muss ich PanResponder in einer benutzerdefinierten Ansicht verwenden, um ein Bild zu vergrößern und es entsprechend zu positionieren. Gibt es einen leichteren Weg?

+0

Sie Ausschau nach können Android Pinch-to-Zoom Bildansicht Bibliotheken und wo es zu einer Reaktion-native Komponente. – whitep4nther

+0

Ich habe das gleiche Problem, @ Leonti, lösen Sie es? – savelichalex

+0

@savelichalex ja, ich musste meine eigene Komponente schreiben, siehe bitte die Antwort – Leonti

Antwort

26

Ich endete rollen meine eigene ZoomableImage Komponente. Bis jetzt es ist schon ziemlich gut ausarbeitet, hier ist der Code:

import React, {Component, PropTypes} from 'react'; 
 
import { Text, View, PanResponder, Image } from 'react-native'; 
 

 
function calcDistance(x1, y1, x2, y2) { 
 
    let dx = Math.abs(x1 - x2) 
 
    let 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), 
 
    }; 
 
} 
 

 
function maxOffset(offset, windowDimension, imageDimension) { 
 
    let max = windowDimension - imageDimension; 
 
    if (max >= 0) { 
 
     return 0; 
 
    } 
 
    return offset < max ? max : offset; 
 
} 
 

 
function calcOffsetByZoom(width, height, imageWidth, imageHeight, zoom) { 
 
    let xDiff = imageWidth * zoom - width; 
 
    let yDiff = imageHeight * zoom - height; 
 
    return { 
 
     left: -xDiff/2, 
 
     top: -yDiff/2, 
 
    } 
 
} 
 

 
class ZoomableImage extends Component { 
 

 
    constructor(props) { 
 
     super(props); 
 

 
     this._onLayout = this._onLayout.bind(this); 
 

 
     this.state = { 
 
      zoom: null, 
 
      minZoom: null, 
 
      layoutKnown: false, 
 
      isZooming: false, 
 
      isMoving: false, 
 
      initialDistance: null, 
 
      initialX: null, 
 
      initalY: null, 
 
      offsetTop: 0, 
 
      offsetLeft: 0, 
 
      initialTop: 0, 
 
      initialLeft: 0, 
 
      initialTopWithoutZoom: 0, 
 
      initialLeftWithoutZoom: 0, 
 
      initialZoom: 1, 
 
      top: 0, 
 
      left: 0 
 
     } 
 
    } 
 

 
    processPinch(x1, y1, x2, y2) { 
 
     let distance = calcDistance(x1, y1, x2, y2); 
 
     let center = calcCenter(x1, y1, x2, y2); 
 

 
     if (!this.state.isZooming) { 
 
      let offsetByZoom = calcOffsetByZoom(this.state.width, this.state.height, 
 
          this.props.imageWidth, this.props.imageHeight, this.state.zoom); 
 
      this.setState({ 
 
       isZooming: true, 
 
       initialDistance: distance, 
 
       initialX: center.x, 
 
       initialY: center.y, 
 
       initialTop: this.state.top, 
 
       initialLeft: this.state.left, 
 
       initialZoom: this.state.zoom, 
 
       initialTopWithoutZoom: this.state.top - offsetByZoom.top, 
 
       initialLeftWithoutZoom: this.state.left - offsetByZoom.left, 
 
      }); 
 

 
     } else { 
 
      let touchZoom = distance/this.state.initialDistance; 
 
      let zoom = touchZoom * this.state.initialZoom > this.state.minZoom 
 
       ? touchZoom * this.state.initialZoom : this.state.minZoom; 
 

 
      let offsetByZoom = calcOffsetByZoom(this.state.width, this.state.height, 
 
       this.props.imageWidth, this.props.imageHeight, zoom); 
 
      let left = (this.state.initialLeftWithoutZoom * touchZoom) + offsetByZoom.left; 
 
      let top = (this.state.initialTopWithoutZoom * touchZoom) + offsetByZoom.top; 
 

 
      this.setState({ 
 
       zoom: zoom, 
 
       left: 0, 
 
       top: 0, 
 
       left: left > 0 ? 0 : maxOffset(left, this.state.width, this.props.imageWidth * zoom), 
 
       top: top > 0 ? 0 : maxOffset(top, this.state.height, this.props.imageHeight * zoom), 
 
      }); 
 
     } 
 
    } 
 

 
    processTouch(x, y) { 
 

 
     if (!this.state.isMoving) { 
 
      this.setState({ 
 
       isMoving: true, 
 
       initialX: x, 
 
       initialY: y, 
 
       initialTop: this.state.top, 
 
       initialLeft: this.state.left, 
 
      }); 
 
     } else { 
 
      let left = this.state.initialLeft + x - this.state.initialX; 
 
      let top = this.state.initialTop + y - this.state.initialY; 
 

 
      this.setState({ 
 
       left: left > 0 ? 0 : maxOffset(left, this.state.width, this.props.imageWidth * this.state.zoom), 
 
       top: top > 0 ? 0 : maxOffset(top, this.state.height, this.props.imageHeight * this.state.zoom), 
 
      }); 
 
     } 
 
    } 
 

 
    _onLayout(event) { 
 
     let layout = event.nativeEvent.layout; 
 

 
     if (layout.width === this.state.width 
 
      && layout.height === this.state.height) { 
 
      return; 
 
     } 
 

 
     let zoom = layout.width/this.props.imageWidth; 
 

 
     let offsetTop = layout.height > this.props.imageHeight * zoom ? 
 
      (layout.height - this.props.imageHeight * zoom)/2 
 
      : 0; 
 

 
     this.setState({ 
 
      layoutKnown: true, 
 
      width: layout.width, 
 
      height: layout.height, 
 
      zoom: zoom, 
 
      offsetTop: offsetTop, 
 
      minZoom: zoom 
 
     }); 
 
    } 
 

 
    componentWillMount() { 
 
     this._panResponder = PanResponder.create({ 
 
      onStartShouldSetPanResponder: (evt, gestureState) => true, 
 
      onStartShouldSetPanResponderCapture: (evt, gestureState) => true, 
 
      onMoveShouldSetPanResponder: (evt, gestureState) => true, 
 
      onMoveShouldSetPanResponderCapture: (evt, gestureState) => true, 
 
      onPanResponderGrant: (evt, gestureState) => {}, 
 
      onPanResponderMove: (evt, gestureState) => { 
 
       let touches = evt.nativeEvent.touches; 
 
       if (touches.length == 2) { 
 
        let touch1 = touches[0]; 
 
        let touch2 = touches[1]; 
 

 
        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: (evt, gestureState) => true, 
 
      onPanResponderRelease: (evt, gestureState) => { 
 
       this.setState({ 
 
        isZooming: false, 
 
        isMoving: false 
 
       }); 
 
      }, 
 
      onPanResponderTerminate: (evt, gestureState) => {}, 
 
      onShouldBlockNativeResponder: (evt, gestureState) => true, 
 
     }); 
 
    } 
 

 
    render() { 
 
     return (
 
      <View 
 
      style={this.props.style} 
 
      {...this._panResponder.panHandlers} 
 
      onLayout={this._onLayout}> 
 
      <Image style={{ 
 
        position: 'absolute', 
 
        top: this.state.offsetTop + this.state.top, 
 
        left: this.state.offsetLeft + this.state.left, 
 
        width: this.props.imageWidth * this.state.zoom, 
 
        height: this.props.imageHeight * this.state.zoom 
 
      }} 
 
      source={this.props.source} /> 
 
      </View> 
 
     ); 
 
    } 
 

 
} 
 

 
ZoomableImage.propTypes = { 
 
    imageWidth: PropTypes.number.isRequired, 
 
    imageHeight: PropTypes.number.isRequired, 
 
    source: PropTypes.object.isRequired, 
 
}; 
 
export default ZoomableImage;

+0

Genau das habe ich gesucht. Vielen Dank! :) –

+2

Danke! Vielleicht solltest du daraus eine Komponente auf Github machen und wir (Leute) werden das verbessern. –

+0

Hallo, ich konnte diese Antwort in der neuesten RN 0.46/Expo nicht funktionieren lassen. Für mich wird onPanResponderMove niemals ausgelöst, wenn sich 2 Finger auf dem Bild befinden. Irgendeine Idee? Kannst du eine Expo/CRNA-App veröffentlichen, die zeigt, wie du deinen Code richtig verwendest? Laut diesem Problem bin ich nicht allein mit dem Problem: https://github.com/ienstern/react-native-image-cropper/issues/8 –

0

Es gibt eine jetzt viel einfache Art und Weise. machen Nur eine ScollView mit minimumZoomScale und maximumZoomScale:

import React, { Component } from 'react'; 
import { AppRegistry, ScrollView, Text } from 'react-native'; 

export default class IScrolledDownAndWhatHappenedNextShockedMe extends Component { 
    render() { 
     return (
     <ScrollView minimumZoomScale={1} maximumZoomScale={5} > 
      <Text style={{fontSize:96}}>Scroll me plz</Text> 
      <Text style={{fontSize:96}}>If you like</Text> 
      <Text style={{fontSize:96}}>Scrolling down</Text> 
      <Text style={{fontSize:96}}>What's the best</Text> 
      <Text style={{fontSize:96}}>Framework around?</Text> 
      <Text style={{fontSize:80}}>React Native</Text> 
     </ScrollView> 
    ); 
    } 
} 

// skip these lines if using Create React Native App 
AppRegistry.registerComponent(
    'AwesomeProject', 
() => IScrolledDownAndWhatHappenedNextShockedMe); 
Verwandte Themen