“在 React Native 中使用相机捕获实现可调整大小和可拖动的内部视图”

问题描述 投票:0回答:1

我正在开发一个 React Native 项目,我需要在其中实现具有特定要求的 camera 功能。我想在相机内有一个可调整大小可拖动内部视图,当我单击照片时,它应该仅捕获该内部视图内的内容。此外,内部视图之外的区域的不透明度应为 0.5。

我尝试过探索各种方法,但一直无法找到适合我的确切需求的解决方案。

有人可以指导我如何在 React Native 中实现此功能吗?任何代码片段、库或建议将不胜感激。

这是参考图片

react-native image draggable react-native-camera react-native-image
1个回答
2
投票

这是工作代码,这可能对您有帮助

import { StyleSheet, Text, View, Dimensions, PanResponder, Animated } 
from 'react-native'
import React, { useEffect, useRef, useState } from 'react'
import { Svg, Defs, Rect, Mask, Circle } from 'react-native-svg';
import { AppColor, deviceDisplaySize } from '../Theme/AppTheme';
import { IcnScaleSize } from '../Assets/Icons/SvgIcon';

let _previousLeft = 0;
let _previousTop = 0;

let bottomRightW = 0;
let bottomRightH = 0

let viewMaskingPosition = { x: 0, y: 0 }

let viewParentMaskingSize = { height: 0, width: 0 }

const MaskView = ({ onChangeUpdated, maskStyle = {} }) => {

const maskingViewCornerRadius = 20
const [boxPosition, setBoxPosition] = useState({ x: 0, y: 0 });

const [maskingScaleSize, setMaskingScaleSize] = useState({ height: 100, width: 100 })

const panResponder = useRef(
    PanResponder.create({
        onStartShouldSetPanResponder: () => true,
        onPanResponderMove: (evt, gestureState) => {
            const { dx, dy } = gestureState;
            const left = _previousLeft + dx;
            const top = _previousTop + dy;

            const minX = 10;
            const maxX = viewParentMaskingSize.width - maskingScaleSize.width
            const minY = 0;
            const maxY = viewParentMaskingSize.height - maskingScaleSize.height
            const boundedX = Math.min(Math.max(left, minX), maxX);
            const boundedY = Math.min(Math.max(top, minY), maxY);

            viewMaskingPosition = { x: boundedX, y: boundedY }
            setBoxPosition({ x: boundedX, y: boundedY });
        },
        onPanResponderRelease: (evt, gestureState) => {
            _previousLeft += gestureState.dx;
            _previousTop += gestureState.dy;
        },
        onPanResponderEnd: (evt, gestureState) => {
            parseCoordinates(gestureState)
        }
    }),
).current;

const scalePanResponder = useRef(
    PanResponder.create({
        onStartShouldSetPanResponder: () => true,
        onPanResponderMove: (event, gestureState) => {

            const minimumLimit = 100
            const maximumLimit = deviceDisplaySize().width - 50

            const width = bottomRightW + gestureState.dx;
            const height = bottomRightH + gestureState.dy;

            const minX = 100;
            const maxX = viewParentMaskingSize.width
            const minY = 100;
            const maxY = viewParentMaskingSize.height
            const boundedX = Math.min(Math.max(width, minX), maxX);
            const boundedY = Math.min(Math.max(height, minY), maxY);

            setMaskingScaleSize({
                width: boundedX,
                height: boundedY
            })
        },
        onPanResponderRelease: (evt, gestureState) => {
            bottomRightW += gestureState.dx;
            bottomRightH += gestureState.dy;
        },
        onPanResponderEnd: (evt, gestureState) => {
            parseCoordinates(gestureState)
        }
    }),
).current;

const onLayout = (event) => {
    const { width, height } = event.nativeEvent.layout;
    viewParentMaskingSize = { width: width, height: height }

    bottomRightW = width - 40
    bottomRightH = height / 2.5

    const latestSize = { width: bottomRightW, height: bottomRightH }

    setMaskingScaleSize(latestSize)

    const newY = (height / 2) - (height / 3)

    viewMaskingPosition = { x: 20, y: newY }
    setBoxPosition({ x: 20, y: newY })

    onChangeUpdated({ ...latestSize, ...viewMaskingPosition })
};

return (
    <View style={{ flex: 1, }} onLayout={onLayout} >

        <Svg height="100%" width="100%" style={[StyleSheet.absoluteFill]}>
            <Defs>
                <Mask id="mask" x="0" y="0" height="100%" width="100%">
                    <Rect height="100%" width="100%" fill="#fff" />
                    <Rect
                        x={boxPosition.x}
                        y={boxPosition.y}
                        height={maskingScaleSize.height}
                        width={maskingScaleSize.width}
                        rx={maskingViewCornerRadius} ry={maskingViewCornerRadius}
                    >
                    </Rect>
                </Mask>
            </Defs>
            <Rect
                height="100%"
                width="100%"
                fill={AppColor.cameraMasking}
                mask="url(#mask)"
                fill-opacity="0"
            />
        </Svg>

        <Animated.View
            style={[
                { height: maskingScaleSize.height, width: maskingScaleSize.width },
                styles.viewMasking,
                { transform: [{ translateX: boxPosition.x }, { translateY: boxPosition.y }] }
            ]}
            {...panResponder.panHandlers}
        >
            <View style={{ height: '100%', width: '100%', }}>

                <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }} >
                    <View style={{ height: 25, borderRadius: 2, borderWidth: 1.5, backgroundColor: 'white', borderColor: 'white', opacity: 0.4 }} />
                    <View style={{ width: 25, borderRadius: 2, borderWidth: 1.5, backgroundColor: 'white', borderColor: 'white', opacity: 0.4, position: 'absolute' }} />
                </View>

                <View style={{ position: 'absolute', height: 45, width: 45, top: -2, left: -2, borderColor: '#FED422', borderTopWidth: 4, borderLeftWidth: 4, borderTopLeftRadius: maskingViewCornerRadius }} />
                <View style={{ position: 'absolute', height: 45, width: 45, top: -2, right: -2, borderColor: '#FED422', borderTopWidth: 4, borderRightWidth: 4, borderTopRightRadius: maskingViewCornerRadius }} />
                <View style={{ position: 'absolute', height: 45, width: 45, bottom: -2, left: -2, borderColor: '#FED422', borderBottomWidth: 4, borderLeftWidth: 4, borderBottomLeftRadius: maskingViewCornerRadius }} />
                <View style={{ position: 'absolute', height: 45, width: 45, bottom: -2, right: -2, borderColor: '#FED422', borderBottomWidth: 4, borderRightWidth: 4, borderBottomRightRadius: maskingViewCornerRadius, backgroundColor: 'red' }}
                    {...scalePanResponder.panHandlers}

                >
                    <IcnScaleSize />
                </View>
            </View>
        </Animated.View>
    </View>
)

function parseCoordinates(gestureState) {
    //Position
    const left = _previousLeft + gestureState.dx;
    const top = _previousTop + gestureState.dy;

    //Scaling 
    const width = bottomRightW + gestureState.dx;
    const height = bottomRightH + gestureState.dy;

    const objSize = { width: width, height: height }
    const objCoordinate = { x: left, y: top }

    onChangeUpdated({ ...objSize, ...viewMaskingPosition })

}

}

export default MaskView
const styles = StyleSheet.create({

viewMasking: {
 borderRadius: 23,
 position: 'relative',
 borderWidth: 1,
 borderColor: '#FED422'
 }
 })  
© www.soinside.com 2019 - 2024. All rights reserved.