【问题标题】:Scrollable image with pinch-to-zoom in react-native在 react-native 中使用捏拉缩放的可滚动图像
【发布时间】:2020-10-21 18:04:30
【问题描述】:

我正在尝试在我的 React Native 应用程序 (Android) 中显示图像,我想让用户能够放大和缩小该图像。 这也要求图像在放大后可以滚动。

我该怎么做?

我尝试使用ScrollView 在内部显示更大的图像,但在 Android 上它可以垂直或水平滚动,而不是双向滚动。 即使这样有效,也存在使pinch-to-zoom 有效的问题。

据我了解,我需要在自定义视图上使用PanResponder 来缩放图像并相应地定位它。有没有更简单的方法?

【问题讨论】:

  • 您可以留意 Android pinch-to-zoom Image View 库并将其映射到 react-native 组件。
  • 我也有同样的问题,@Leonti,你解决了吗?
  • @savelichalex 是的,我必须自己写组件,请看答案

标签: react-native


【解决方案1】:

我最终推出了自己的 ZoomableImage 组件。到目前为止一切正常,代码如下:

import React, { Component } from "react";
import { View, PanResponder, Image } from "react-native";
import PropTypes from "prop-types";

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)
  };
}

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

function calcOffsetByZoom(width, height, imageWidth, imageHeight, zoom) {
  const xDiff = imageWidth * zoom - width;
  const 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) {
const distance = calcDistance(x1, y1, x2, y2);
const center = calcCenter(x1, y1, x2, y2);

if (!this.state.isZooming) {
  const 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 {
  const touchZoom = distance / this.state.initialDistance;
  const zoom =
    touchZoom * this.state.initialZoom > this.state.minZoom
      ? touchZoom * this.state.initialZoom
      : this.state.minZoom;

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

  this.setState({
    zoom,
    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 {
  const left = this.state.initialLeft + x - this.state.initialX;
  const 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) {
const layout = event.nativeEvent.layout;

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

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

const 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,
  offsetTop,
  minZoom: zoom
});
  }

  componentWillMount() {
this._panResponder = PanResponder.create({
  onStartShouldSetPanResponder: () => true,
  onStartShouldSetPanResponderCapture: () => true,
  onMoveShouldSetPanResponder: () => true,
  onMoveShouldSetPanResponderCapture: () => 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: () => true,
  onPanResponderRelease: () => {
    this.setState({
      isZooming: false,
      isMoving: false
    });
  },
  onPanResponderTerminate: () => {},
  onShouldBlockNativeResponder: () => 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;

【讨论】:

  • 谢谢!也许你应该从这个在 github 上创建一个组件,我们(人们)会改进它。
  • 嗨,我无法让这个答案在最新的 RN 0.46 / Expo 中起作用。对我来说,当图像上有 2 个手指时,onPanResponderMove 永远不会被触发。任何想法?您能否发布一个展示如何正确使用您的代码的 Expo/CRNA 应用程序?根据这个问题,我并不孤单:github.com/stoffern/react-native-image-cropper/issues/8
  • 谁能确认这个解决方案曾经奏效过? PanResponder 中有一个支持多点触控的功能请求:react-native.canny.io/feature-requests/p/…
  • 它似乎只在 Android 上对我有用。它需要在包装视图上设置宽度和高度,具体值或 100%。
  • 对我来说它有效,虽然互动很密切,但没有雪茄。 @Nik 你是不是import PropTypes from 'prop-types'; 你现在需要从一个单独的包中导入它,而不是像主包中的import React, {Component, PropTypes} from 'react'; 那样
【解决方案2】:

现在有一个更简单的方法。 只需使用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);

【讨论】:

  • 同意,不适用于 Android。 (世博会 28.0,RN 0.55.4)
  • minimumZoomScale 和 maximumZoomScale 仅在 iOS 中受支持。
【解决方案3】:

就我而言,我必须在 Viewpager 中添加具有缩放功能的图像。

所以我用过这两个库。

import ViewPager from '@react-native-community/viewpager'
import PhotoView from 'react-native-photo-view-ex';

您可以从中安装。

npm i @react-native-community/viewpager
npm i react-native-photo-view-ex

所以我使用了这个代码。

class ResumeView extends React.Component {

    render() {
        preivewArray = this.props.showPreview.previewArray
        var pageViews = [];
        for (i = 0; i < preivewArray.length; i++) {
            pageViews.push(<View style={style.page}>

                <PhotoView
                    source={{ uri: preivewArray[i].filePath }}
                    minimumZoomScale={1}
                    maximumZoomScale={3}
                    // resizeMode='stretch'
                    style={{ width: a4_width, height: a4_height, alignSelf: 'center' }} />

            </View>);
        }

        return (
            <ViewPager
                onPageScroll={this.pageScroll}
                style={{ width: '100%', height: a4_height }}>
                {pageViews}
            </ViewPager>
        )
    }

    pageScroll = (event) => {
        console.log("onPageScroll")
    }

}

【讨论】:

  • previewArray 是什么?
  • preivewArray 数组是,images 数组,键为:filePath
【解决方案4】:

您可以简单地使用 react-native-image-zoom-viewerreact-native-image-pan-zoom 库。使用这个库,您不必手动编写代码。

【讨论】:

  • react-native-image-zoom-viewer 非常稳定和有用。我同意你的看法。
【解决方案5】:
npm i react-native-photo-view-ex
import PhotoView from 'react-native-photo-view-ex';
<PhotoView
    style={{ flex: 1, width: '100%', height: '100%' }}
    source={{ uri: this.state.filePath }} // you can supply any URL as well
    minimumZoomScale={1} // max value can be 1
    maximumZoomScale={2} // max value can be 3
/>

【讨论】:

    【解决方案6】:

    如果您与 react-native 合作,请不要深入,因为随着您深入,事情会变得越来越复杂。

    试一试...

    npm i react-native-image-zoom-viewer --save
    

    yarn add react-native-image-zoom-viewer
    

    复制此代码并将其放入 app.js 并点击运行按钮。

    import React from 'react';
    import {View} from 'react-native';
    import ImageViewer from 'react-native-image-zoom-viewer';
    const image = [
      {
        url:
          'https://static8.depositphotos.com/1020341/896/i/950/depositphotos_8969502-stock-photo-human-face-with-cracked-texture.jpg',
      },
    ];
    
    const App = () => {
      return (
        <View style={{flex: 1}}>
          <ImageViewer imageUrls={image} />
        </View>
      );
    };
    export default App;
    

    【讨论】:

    • 我在最新的 react-native 应用程序中尝试了相同的代码,但它没有缩放,我必须为此传递道具吗?
    • 不工作!图像未显示。只有黑屏。
    • @cmcodes 它仅适用于 react-nativereact-native-image-zoom-viewer 上的旧版本。重新检查此问题后,我将在几天内更新我的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-08-26
    • 2018-02-07
    • 2022-10-18
    • 2014-01-07
    • 2017-06-29
    • 2014-01-28
    • 2021-09-04
    相关资源
    最近更新 更多