我正在开发一个 React Native 项目,我需要在其中实现具有特定要求的 camera 功能。我想在相机内有一个可调整大小和可拖动内部视图,当我单击照片时,它应该仅捕获该内部视图内的内容。此外,内部视图之外的区域的不透明度应为 0.5。
我尝试过探索各种方法,但一直无法找到适合我的确切需求的解决方案。
有人可以指导我如何在 React Native 中实现此功能吗?任何代码片段、库或建议将不胜感激。
这是工作代码,这可能对您有帮助
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'
}
})