在 react-native 中缩放的可滚动图像 [英] Scrollable image with pinch-to-zoom in react-native

查看:64
本文介绍了在 react-native 中缩放的可滚动图像的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to display an image in my React Native app (Android) and I want to give users an ability to zoom that image in and out. This also requires the image to be scrollable once zoomed in.

我该怎么做?

我尝试使用 ScrollView 在里面显示更大的图像,但在 Android 上它可以垂直或水平滚动,而不是双向滚动.即使这样做有效,也存在使 捏合缩放 起作用的问题.

I tried to use ScrollView to display a bigger image inside, but on Android it can either scroll vertically or horizontally, not both ways. Even if that worked there is a problem of making pinch-to-zoom work.

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

As far as I understand I need to use PanResponder on a custom view to zoom an image and position it accordingly. Is there an easier way?

推荐答案

我最终推出了自己的 ZoomableImage 组件.到目前为止,它已经运行得很好,这是代码:

I ended up rolling my own ZoomableImage component. So far it's been working out pretty well, here is the code:

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;

这篇关于在 react-native 中缩放的可滚动图像的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆