2016-04-02 15 views
5

私はReact Nativeアプリ(Android)で画像を表示しようとしています。ユーザーにその画像をズームイン/ズームインする機能を与えたいと思います。 ズームインすると画像をスクロール可能にする必要があります。ピンチトゥーズームでスクロール可能な画像

どうすればいいですか?

私は大きな画像を表示するためにScrollViewを使用しようとしましたが、Androidでは両方向ではなく縦横にスクロールできます。 それが働いても、pinch-to-zoomを動作させるという問題があります。

私が理解する限り、カスタムビューでPanResponderを使用して画像をズームし、それに応じて配置する必要があります。簡単な方法がありますか?

+0

あなたはAndroidのピンチ・ツー・ズーム画像を表示ライブラリの外を見ると反応し、ネイティブコンポーネントにマップすることができます。 – whitep4nther

+0

私は同じ問題を抱えています、@レオンティ、あなたはそれを解決しますか? – savelichalex

+0

@savelichalexはい、私自身のコンポーネントを書く必要がありました。答えは – Leonti

答えて

26

私は自分自身のZoomableImageコンポーネントのローリングを終了しました。

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

です。これは私が探していたものです。ありがとうございました! :) –

+2

ありがとう!おそらくあなたはこれからgithub上のコンポーネントを作成する必要があり、我々(人々)はそれを改善するでしょう。 –

+0

こんにちは、この回答は最新のRN 0.46/Expoで動作させることができませんでした。私にとっては、画像上に2本の指がある場合、 'onPanResponderMove'は決して起動されません。何か案が?あなたのコードを正しく使う方法を示すExpo/CRNAアプリを公開できますか?この問題によれば、私は問題を抱えているだけではありません:https://github.com/stoffern/react-native-image-cropper/issues/8 –

0

は今、もっと簡単な方法があります:これまでのところ、それはかなりうまく働いています、ここのコードです。 ただminimumZoomScalemaximumZoomScaleでScollViewします

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); 
関連する問題