放大后如何查看图像。(Expo / ReactNative)

时间:2019-11-29 09:36:26

标签: javascript react-native expo

在我的Expo应用程序中的特定屏幕上,您可以拍摄图像或使用expo API从相机胶卷中进行选择,然后进行预览。在预览中,我有一个叠加层,用户可以在其上触摸图像上的一点,在相应的X,Y轴上显示点/标记。

我希望能够放大图像并更精确地放置标记。

到目前为止,我已经尝试过:

  1. Scrollview-有点用,但是保存图像并再次查看后,标记不再位于正确的位置。

  2. https://kmagiera.github.io/react-native-gesture-handler/docs/handler-pan.html:这可以更好地缩放并保持准确性,但是一旦缩放,我将无法平移缩放后的图像。

3。https://github.com/ascoders/react-native-image-zoom:当您尝试平移并失去标记位置时,它会出现小故障。

理想情况下,我正在考虑使用选项2,但需要进行更改,以便可以平移图像。

有人在工作中有类似的例子或建议吗?

import React from "react";
import {
  Container,
  Text,
  Button,
  View,
  Icon,
  StyleProvider,
} from "native-base";
import {
  ImageBackground,
  Image,
  TouchableOpacity,
  TextInput,
  Animated,
  StyleSheet,
  Dimensions,
} from "react-native";

import {
  PinchGestureHandler,
  PanGestureHandler,
  ScrollView,
} from "react-native-gesture-handler";

import styles from "./ShotLoggerStyles";
import texts from "../../Style/texts";

import getTheme from "../../native-base-theme/components";
import marksmenOne from "../../native-base-theme/variables/marksmenOne";

import HeaderComponent from "../../Components/Header/Header";
import FooterComponent from "../../Components/Footer/FooterComponent";
import ChangeWeapon from "../../Components/ChangeWeapon";
import Loader from "../../Components/Loader/Loader";

const mainBG = require("../../assets/Backgrounds/metal.jpg");
const contentBG = require("../../assets/Backgrounds/training.jpg");

const windowWidth = Dimensions.get("window").width;

const ShotLoggerView = props => {
  const {
    leftPress,
    log,
    addShot,
    removeShot,
    updateShots,
    updateScore,
    changeWeaponHandler,
    _takeImageHandler,
    saveLog,
    weaponModalVisible,
    toggleWeaponModal,
    loading,
    displayHeight,
    pinchRef,
    rotationRef,
    _onPinchGestureEvent,
    _onPinchHandlerStateChange,
    _scale,
    panRef,
    _onTiltGestureEvent,
    _onTiltGestureStateChange,
    minDist,
    minPointers,
    maxPointers,
    avgTouches,
    _tiltStr,
  } = props;

  return (
    <Container>
      <StyleProvider style={getTheme(marksmenOne)}>
        <ImageBackground source={mainBG} style={styles.mainBG}>
          <Loader visible={loading} />
          <HeaderComponent
            headerTitle="Logging Shots"
            leftPress={leftPress}
            leftAction="close"
          />
          <View style={styles.content}>
            {!log.image ? (
              <>
                <ChangeWeapon
                  weaponModalVisible={weaponModalVisible}
                  onChangeWeapon={changeWeaponHandler}
                  toggleWeaponModal={toggleWeaponModal}
                />
                <ImageBackground style={styles.noImage} source={contentBG}>
                  <Text style={texts.headerTitle}>
                    Take a picture of your target
                  </Text>

                  <Button
                    icon
                    style={styles.buttonPrimary}
                    onPress={() => _takeImageHandler()}
                  >
                    <Icon style={styles.buttonIcon} name="camera" />
                  </Button>
                </ImageBackground>
              </>
            ) : (
              <>
                <View style={styles.details}>
                  <View style={styles.detailsItem}>
                    <Text style={texts.bodyWhite}>Shots</Text>
                    <TextInput
                      style={styles.input}
                      defaultValue={log.shots.toString()}
                      editable={false}
                    />
                  </View>
                  <View style={styles.detailsItem}>
                    <Text style={texts.bodyWhite}>Score</Text>
                    <TextInput
                      style={styles.input}
                      onChangeText={value => updateScore(value)}
                      clearTextOnFocus
                      keyboardType="number-pad"
                      returnKeyType="done"
                    />
                  </View>
                </View>
                <ScrollView>
                  <PanGestureHandler
                    ref={panRef}
                    onGestureEvent={_onTiltGestureEvent}
                    onHandlerStateChange={_onTiltGestureStateChange}
                    //   onGestureEvent={this._onDragGestureEvent}
                    // onHandlerStateChange={this._onDragHandlerStateChange}
                    minDist={1}
                    minPointers={2}
                    maxPointers={2}
                    avgTouches
                  >
                    <Animated.View
                      style={[
                        styles.style2,
                        {
                          transform: [
                            { perspective: 200 },
                            { scale: _scale },
                            { rotateX: _tiltStr },
                          ],
                        },
                      ]}
                      collapsable={false}
                    >
                      <PinchGestureHandler
                        ref={pinchRef}
                        simultaneousHandlers={rotationRef}
                        onGestureEvent={_onPinchGestureEvent}
                        onHandlerStateChange={_onPinchHandlerStateChange}
                      >
                        <Animated.View
                          style={[
                            styles.style2,
                            {
                              transform: [
                                { perspective: 200 },
                                { scale: _scale },
                                { rotateX: _tiltStr },
                              ],
                            },
                          ]}
                          collapsable={false}
                        >
                          <TouchableOpacity
                            activeOpacity={1}
                            style={styles.pointer}
                            onPress={evt => addShot(evt.nativeEvent)}
                          >
                            <View
                              style={[
                                styles.instructions,
                                log.heatmap.length > 0
                                  ? { zIndex: -5 }
                                  : { zIndex: 20 },
                              ]}
                            >
                              <Text style={texts.headerBody}>
                                Tap the picture to add &amp; remove shots.
                              </Text>
                            </View>

                            <Animated.Image
                              style={{
                                width: "100%",
                                minHeight: props.displayHeight,
                                height: "auto",
                              }}
                              source={{
                                uri: `data:image/jpg;base64,${log.image}`,
                              }}
                            />
                            {log.heatmap.length > 0 &&
                              log.heatmap.map((shot, index) => {
                                return (
                                  <TouchableOpacity
                                    key={index}
                                    onPress={() => removeShot(index)}
                                    style={[
                                      styles.shot,
                                      { left: shot.x - 10, top: shot.y - 10 },
                                    ]}
                                  />
                                );
                              })}
                          </TouchableOpacity>
                        </Animated.View>
                      </PinchGestureHandler>
                    </Animated.View>
                  </PanGestureHandler>
                </ScrollView>
              </>
            )}
          </View>
          <FooterComponent
            rightAction={saveLog}
            rightActionText="Save shots"
            rightActionDisable={!log.image}
          />
        </ImageBackground>
      </StyleProvider>
    </Container>
  );
};

export default ShotLoggerView;

const styles2 = StyleSheet.create({
  container: {
    ...StyleSheet.absoluteFillObject,
    backgroundColor: "black",
    overflow: "hidden",
    alignItems: "center",
    flex: 1,
    justifyContent: "center",
  },
  pinchableImage: {
    width: 250,
    height: 250,
  },
  wrapper: {
    flex: 1,
  },
});

/* eslint-disable no-underscore-dangle */
import React, { Component } from "react";
import { Alert, Dimensions, Animated, PanResponder } from "react-native";
import * as ImagePicker from "expo-image-picker";
import Constants from "expo-constants";

import { State } from "react-native-gesture-handler";

import * as Permissions from "expo-permissions";
import * as ImageManipulator from "expo-image-manipulator";
import * as Segment from "expo-analytics-segment";
import ShotLoggerView from "../ShotLoggerView";
import ENV from "../../../env";

import { USE_NATIVE_DRIVER } from "../../../config";

const windowWidth = Dimensions.get("window").width;
const circleRadius = 30;

Segment.initialize({
  androidWriteKey: ENV.SEGMENT_ANDROID_KEY,
  iosWriteKey: ENV.SEGMENT_IOS_KEY,
});

class ShotLoggerContainer extends Component {
  _panResponder = {};

  _previousLeft = 0;

  _previousTop = 0;

  _circleStyles = {};

  panRef = React.createRef();

  pinchRef = React.createRef();

  tapRef = React.createRef();

  constructor(props) {
    super(props);

    /* Panning */
    this._touchX = new Animated.Value(windowWidth / 2 - circleRadius);
    this._translateX = Animated.add(
      this._touchX,
      new Animated.Value(-circleRadius)
    );
    this._onPanGestureEvent = Animated.event(
      [
        {
          nativeEvent: {
            x: this._touchX,
          },
        },
      ],
      { useNativeDriver: USE_NATIVE_DRIVER }
    );

    /* Pinching */
    this._baseScale = new Animated.Value(1);
    this._pinchScale = new Animated.Value(1);
    this._scale = Animated.multiply(this._baseScale, this._pinchScale);
    this._lastScale = 1;
    this._onPinchGestureEvent = Animated.event(
      [{ nativeEvent: { scale: this._pinchScale } }],
      { useNativeDriver: USE_NATIVE_DRIVER }
    );

    /* Tilt */
    this._tilt = new Animated.Value(0);
    this._tiltStr = this._tilt.interpolate({
      inputRange: [-501, -500, 0, 1],
      outputRange: ["1rad", "1rad", "0rad", "0rad"],
    });
    this._lastTilt = 0;
    this._onTiltGestureEvent = Animated.event(
      [{ nativeEvent: { translationY: this._tilt } }],
      { useNativeDriver: USE_NATIVE_DRIVER }
    );

    this.state = {
      weaponModalVisible: false,
      log: {
        heatmap: [],
        shots: 0,
        score: 0,
        weapon_id: 0,
        diary_id: 0,
        image: undefined,
        imageSize: {
          width: 768,
          height: 1024,
        },
      },
      loading: false,
      displayHeight: 1024,
    };
  }

  componentWillMount() {
    this._panResponder = PanResponder.create({
      onStartShouldSetPanResponder: this._handleStartShouldSetPanResponder,
      onMoveShouldSetPanResponder: this._handleMoveShouldSetPanResponder,
      onPanResponderGrant: this._handlePanResponderGrant,
      onPanResponderMove: this._handlePanResponderMove,
      onPanResponderRelease: this._handlePanResponderEnd,
      onPanResponderTerminate: this._handlePanResponderEnd,
    });
    this._previousLeft = 20;
    this._previousTop = 84;
    this._circleStyles = {
      style: {
        left: this._previousLeft,
        top: this._previousTop,
        backgroundColor: "green",
      },
    };
  }

  componentDidMount() {
    this.getPermissionAsync();
    this.state.log.weapon_id = this.props.shootingSession.currentWeapon.id;
    this.state.log.diary_id = this.props.shootingSession.id;
    this._updateNativeStyles();
  }

  getPermissionAsync = async () => {
    if (Constants.platform.ios) {
      const { status } = await Permissions.askAsync(
        Permissions.CAMERA_ROLL,
        Permissions.CAMERA
      );
      if (status !== "granted") {
        Alert.alert(
          "Sorry, we need camera and camera roll permissions to make this work!"
        );
      }
    }
  };

  _onTapHandlerStateChange = ({ nativeEvent }) => {
    if (nativeEvent.oldState === State.ACTIVE) {
      // Once tap happened we set the position of the circle under the tapped spot
      this._touchX.setValue(nativeEvent.x);
    }
  };

  _onPinchHandlerStateChange = event => {
    if (event.nativeEvent.oldState === State.ACTIVE) {
      this._lastScale *= event.nativeEvent.scale;
      this._baseScale.setValue(this._lastScale);
      this._pinchScale.setValue(1);
    }
  };

  _onTiltGestureStateChange = event => {
    if (event.nativeEvent.oldState === State.ACTIVE) {
      this._lastTilt += event.nativeEvent.translationY;
      this._tilt.setOffset(this._lastTilt);
      this._tilt.setValue(0);
    }
  };

  changeWeaponHandler = () => {
    this.setState({
      weaponModalVisible: true,
    });
  };

  toggleWeaponModal = () => {
    this.setState({
      weaponModalVisible: false,
    });
  };

  _takeImageHandler = async () => {
    this.setState({
      loading: true,
    });
    const result = await ImagePicker.launchCameraAsync({
      mediaTypes: ImagePicker.MediaTypeOptions.Images,
      allowsEditing: false,
      aspect: [4, 3],
      quality: 1,
      exif: true,
    });
    if (!result.cancelled) {
      const manipResult = await ImageManipulator.manipulateAsync(
        result.uri,
        [{ resize: { height: 1024 } }],
        {
          compress: 0.9,
          format: ImageManipulator.SaveFormat.JPEG,
          base64: true,
        }
      );
      this.state.log.image = manipResult.base64;
      // Calculate resized width
      this.state.log.imageSize.width = (1024 * result.width) / result.height;
      const deviceWidth = Dimensions.get("window").width;
      const displayHeight = (deviceWidth * result.height) / result.width;
      this.setState({
        loading: false,
        displayHeight,
      });
      this.forceUpdate();
    }
  };

  saveLog = () => {
    Alert.alert("Save Log", "Are you sure you want to save this shot log?", [
      {
        text: "Cancel",
        style: "cancel",
      },
      {
        text: "CONFIRM",
        onPress: () => {
          this.realSave();
        },
      },
    ]);
  };

  realSave = async () => {
    this.setState({
      loading: true,
    });
    this.props.shootingSession.logs.push(this.state.log);
    this.props.shootingSession.score =
      Number(this.props.shootingSession.score) + Number(this.state.log.score);
    this.props.shootingSession.shots =
      this.props.shootingSession.shots + this.state.log.shots;
    this.props
      .addDiaryLog(this.props.user.auth, this.state.log)
      .then(result => {
        Segment.trackWithProperties("Shots logging saved", {
          version: "Beta",
        });
        this.goBack();
      })
      .catch(error => {
        this.setState({
          loading: false,
        });
        console.log("error", error);
      });
  };

  addShot = event => {
    const logA = JSON.parse(JSON.stringify(this.state.log));
    logA.heatmap.push({ x: event.locationX, y: event.locationY, value: 85 });
    logA.shots++;
    this.setState({ log: logA });
  };

  removeShot = index => {
    const logR = JSON.parse(JSON.stringify(this.state.log));
    logR.heatmap.splice(index, 1);
    logR.shots--;
    this.setState({ log: logR });
  };

  updateShots = value => {
    this.state.log.shots = value;
  };

  updateScore = value => {
    this.state.log.score = value;
  };

  goBack = () => {
    this.props.navigation.goBack();
  };

  leftPress = () => {
    Alert.alert("Abort drill?", "Image and data will be lost.", [
      {
        text: "Cancel",
        style: "cancel",
      },
      {
        text: "Abort",
        onPress: () => {
          Segment.trackWithProperties("Shots logging cancel", {
            version: "Beta",
          });
          this.goBack();
        },
      },
    ]);
  };

  _highlight = () => {
    this._circleStyles.style.backgroundColor = "blue";
    this._updateNativeStyles();
  };

  _unHighlight = () => {
    this._circleStyles.style.backgroundColor = "green";
    this._updateNativeStyles();
  };

  _updateNativeStyles = () => {
    this.circle && this.circle.setNativeProps(this._circleStyles);
  };

  _handleStartShouldSetPanResponder = (e, gestureState) => {
    // Should we become active when the user presses down on the circle?
    return true;
  };

  _handleMoveShouldSetPanResponder = (e, gestureState) => {
    // Should we become active when the user moves a touch over the circle?
    return true;
  };

  _handlePanResponderGrant = (e, gestureState) => {
    this._highlight();
  };

  _handlePanResponderMove = (e, gestureState) => {
    this._circleStyles.style.left =
      this._previousLeft + gestureState.dx * (I18nManager.isRTL ? -1 : 1);
    this._circleStyles.style.top = this._previousTop + gestureState.dy;
    this._updateNativeStyles();
  };

  _handlePanResponderEnd = (e, gestureState) => {
    this._unHighlight();
    this._previousLeft += gestureState.dx * (I18nManager.isRTL ? -1 : 1);
    this._previousTop += gestureState.dy;
  };

  render() {
    const { weaponModalVisible } = this.state;
    Segment.screenWithProperties("Shotlogger", {
      version: "Beta",
    });
    return (
      <ShotLoggerView
        setState={data => this.setState(data)}
        goBack={this.goBack}
        log={this.state.log}
        leftPress={this.leftPress}
        addShot={event => this.addShot(event)}
        removeShot={event => this.removeShot(event)}
        updateShots={value => this.updateShots(value)}
        updateScore={value => this.updateScore(value)}
        _takeImageHandler={this._takeImageHandler}
        saveLog={() => this.saveLog()}
        changeWeaponHandler={this.changeWeaponHandler}
        weaponModalVisible={weaponModalVisible}
        toggleWeaponModal={this.toggleWeaponModal}
        loading={this.state.loading}
        displayHeight={this.state.displayHeight}
        _onPinchHandlerStateChange={event =>
          this._onPinchHandlerStateChange(event)
        }
        pinchRef={this.pinchRef}
        rotationRef={this.rotationRef}
        _onPinchGestureEvent={this._onPinchGestureEvent}
        _scale={this._scale}
        _tiltStr={this._tiltStr}
        panRef={this.panRef}
        _onTiltGestureEvent={this._onTiltGestureEvent}
        _onTiltGestureStateChange={this._onTiltGestureStateChange}
        minDist={this.minDist}
        minPointers={this.minPointers}
        maxPointers={this.maxPointers}
        avgTouches={this.avgTouches}
        tapRef={this.tapRef}
        panRef={this.panRef}
      />
    );
  }
}
export default ShotLoggerContainer;
  Expo CLI 3.9.1 environment info:
    System:
      OS: macOS 10.15.1
      Shell: 5.7.1 - /bin/zsh
    Binaries:
      Node: 12.8.0 - ~/.nvm/versions/node/v12.8.0/bin/node
      Yarn: 1.19.1 - /usr/local/bin/yarn
      npm: 6.11.3 - ~/.nvm/versions/node/v12.8.0/bin/npm
      Watchman: 4.9.0 - /usr/local/bin/watchman
    IDEs:
      Android Studio: 3.5 AI-191.8026.42.35.5791312
      Xcode: 11.2/11B41 - /usr/bin/xcodebuild
    npmPackages:
      @types/react-native: ^0.60.22 => 0.60.22 
      expo: ^35.0.1 => 35.0.1 
      react: 16.8.3 => 16.8.3 
      react-native: https://github.com/expo/react-native/archive/sdk-35.0.0.tar.gz => 0.59.8 
      react-navigation: ^3.13.0 => 3.13.0 
    npmGlobalPackages:
      expo-cli: 3.9.1

0 个答案:

没有答案