React Native 中无损图像裁剪;有边界缩放和平移

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

我的目标是在我的 React Native 应用程序中实现诸如无损图像裁剪之类的功能,其中保持纵横比。作为这一点的代理,我认为我可以允许用户通过手势缩放和平移图像,然后保留缩放和平移值。

这一切都工作正常。当我尝试限制平移以使图像无法平移超出其边界时,我遇到了障碍。我最初的想法是这样做:

const maximumXOffset = ((scale.value - 1) / 2) * originalWidth
const maximumYOffset = ((scale.value - 1) / 2) * originalHeight

但这并没有达到预期效果。然后,我尝试存储原始图像大小和缩放后的图像大小,并将偏移量限制为差异的 1/2,但这也不起作用 - 图像仍然可以平移超过其边界。

该组件的大部分完整代码如下。我在这里缺少什么?

// imports elided

function SingleCard({
    uri,
    dateString,
    id,
    canZoom,
}) {
    const dispatch = useDispatch()
   
    const selectTransformsForId = useMemo(makeSelectTransformsForId, [])
    const transformsForId = useSelector((state) =>
        selectTransformsForId(state, id)
    )

    const updateRedux = useCallback(
        (transforms) => {
            dispatch(updateTransforms({ id, transforms }))
        },
        [dispatch, id]
    )

    const zoom = useSharedValue(transformsForId.scale)
    const zoomStart = useSharedValue(zoom.value)
    const offset = useSharedValue({
        x: transformsForId.translateX,
        y: transformsForId.translateY,
    })
    const offsetStart = useSharedValue({ x: offset.value.x, y: offset.value.y })
    const originalSize = useSharedValue({ height: 0, width: 0 })
    const currentSize = useSharedValue({ height: 0, width: 0 })

    const zoomStyle = useAnimatedStyle(() => ({
        transform: [
            { scale: zoom.value },
            { translateX: offset.value.x },
            { translateY: offset.value.y },
        ],
    }))

    const containerRef = useAnimatedRef()
    const imageRef = useAnimatedRef()

    const zoomGesture = Gesture.Pinch()
        .onStart(() => {
            zoomStart.value = zoom.value
            const measurement = measure(containerRef)
            originalSize.value = {
                height: measurement.height,
                width: measurement.width,
            }
        })
        .onUpdate((e) => {
            zoom.value = Math.max(zoomStart.value * e.scale, 1)

            const imageMeasurement = measure(imageRef)
            currentSize.value = {
                height: imageMeasurement.height,
                width: imageMeasurement.width,
            }
        })
        .onEnd(() => {
            runOnJS(updateRedux)({ scale: zoom.value })
        })
        .enabled(canZoom)
        .shouldCancelWhenOutside(false)

    const dragGesture = Gesture.Pan()
        .averageTouches(true)
        .onStart(() => {
            offsetStart.value = offset.value
        })
        .onUpdate((e) => {
            const maximumXOffset =
                (currentSize.value.width - originalSize.value.width) / 2
            const maximumYOffset =
                (currentSize.value.height - originalSize.value.height) / 2

            const xOffset = Math.min(
                Math.max(e.translationX + offsetStart.value.x, -maximumXOffset),
                maximumXOffset
            )
            const yOffset = Math.min(
                Math.max(e.translationY + offsetStart.value.y, -maximumYOffset),
                maximumYOffset
            )
            offset.value = {
                x: xOffset,
                y: yOffset,
            }
        })
        .onEnd(() => {
            runOnJS(updateRedux)({
                translateX: offset.value.x,
                translateY: offset.value.y,
            })
        })
        .minPointers(2)
        .shouldCancelWhenOutside(false)
        .enabled(canZoom)

    const composed = Gesture.Simultaneous(dragGesture, zoomGesture)

    return (
        <View ref={containerRef}>
            <GestureDetector gesture={composed}>
                <AnimatedImage
                    style={[styles.image, zoomStyle]}
                    source={{ uri }}
                    ref={imageRef}
                >        
            </GestureDetector>
        </View>
    )
}

const styles = StyleSheet.create({
    image: {
        width: '100%',
        aspectRatio: 3 / 4,
        position: 'relative',
    }
})

export default memo(SingleCard)
javascript react-native image crop gesture
1个回答
0
投票

所以这里的方法很好,只是边界计算不正确。工作手势代码如下所示:

const zoomGesture = Gesture.Pinch()
        .onStart(() => {
            zoomStart.value = zoom.value
            const measurement = measure(containerRef)
            originalSize.value = {
                height: measurement.height,
                width: measurement.width,
            }
        })
        .onUpdate((e) => {
            zoom.value = Math.max(zoomStart.value * e.scale, 1)
        })
        .onEnd(() => {
            runOnJS(updateState)({ scale: zoom.value })
        })
        .enabled(canZoom)
        .shouldCancelWhenOutside(false)

    const dragGesture = Gesture.Pan()
        .averageTouches(true)
        .onStart(() => {
            offsetStart.value = { x: offsetX.value, y: offsetY.value }
        })
        .onUpdate((e) => {
            const maximumXOffset =
                ((zoom.value - 1) * originalSize.value.width) / (zoom.value * 2)
            const maximumYOffset =
                ((zoom.value - 1) * originalSize.value.height) /
                (zoom.value * 2)

            const xOffset = Math.min(
                Math.max(e.translationX + offsetStart.value.x, -maximumXOffset),
                maximumXOffset
            )
            const yOffset = Math.min(
                Math.max(e.translationY + offsetStart.value.y, -maximumYOffset),
                maximumYOffset
            )
            offsetX.value = xOffset
            offsetY.value = yOffset

            offsetPercent.value = {
                x: offsetX.value / originalSize.value.width,
                y: offsetY.value / originalSize.value.height,
            }
        })
        .onEnd(() => {
            runOnJS(updateState)({
                translateX: offsetX.value,
                translateY: offsetY.value,
                offsetPercentX: offsetPercent.value.x,
                offsetPercentY: offsetPercent.value.y,
            })
        })
        .minPointers(2)
        .shouldCancelWhenOutside(false)
        .enabled(canZoom)
© www.soinside.com 2019 - 2024. All rights reserved.