我试图在我的React原生应用程序(Android)中显示一幅图像,我想让用户能够放大和缩小该图像.

我该怎么办?

我试着用ScrollView在里面显示更大的图像,但在Android上它可以垂直或水平滚动,而不是双向滚动.

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

推荐答案

我最终推出了自己的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;

React-native相关问答推荐

对iOS通知上未显示的本机映像做出react

React本机组件未更新状态

为什么当我更换屏幕时playground 音乐不会停止?

React Native 在每个页面加载时调用数据库

如何渲染一个 svg 以在不同的窗口大小下动态地看起来相同?

RNMK - 超级表达式必须为空或函数

react-native (expo)加载markdown 文件

为应用创建单个 MobX store 的最佳方式是什么?

Visual Studio 代码错误:-Failed to start flow Error: Wrong version of Flow. The config specifies version ^0.92.0 but this is version 0.95.1

react-native 链接仅适用于一个项目(Android 或 iOS)

无法访问函数内部的状态

React Native 有全局作用scope域吗?如果没有,我该如何模仿它?

使用 React Navigation 制作离开屏幕的动画

你如何从 React Native 的 EventEmitter 实例中移除一个监听器?

React Native 检测点击 View

如何在 react-native 中从 AsyncStorage 中删除元素

更改按钮 colored颜色 onPress(切换功能)

React 在任何根目录中都找不到入口文件

NavigationActions.reset 不是函数吗?

如何修复 npm 审计修复问题?