react-native 实现图片双指缩放,点击关闭功能

1,396 阅读2分钟

应用需求,需要实现类似微信的图片缩放功能。鼓捣了一下,踩了点坑,最后还是感觉有问题,不过还是分享一下代码。希望老哥们斧正。

创作不易,点个赞就是对作者最大支持

首先贴下实现效果

Kapture 2022-07-28 at 14.45.57.gif

以下为用到的插件

/** 动画插件 */
react-native-reanimated
/** 手势库  */
react-native-gesture-handler
/** 图片缓存 */
react-native-fast-image

详细用法请自己查询相关文档

详细代码如下

/** 任一传送门覆盖层 */
import { Overlay } from 'xxx';
import React, { useEffect } from 'react';
import { Dimensions, ScrollView, StyleSheet, View } from 'react-native';
import {
  Gesture,
  GestureDetector,
  GestureHandlerRootView,
} from 'react-native-gesture-handler';
import Animated, {
  runOnJS,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';
import useMediaSource from '@/utils/hooks/useMediaSource';
/** 对图片缓存的封装,替换成自己的图片就好 */
import FastImageWithDefault from '../FastImageWithDefault';

const styles = StyleSheet.create({
  overlay: {
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#000',
  },
  contentWrapper: {
    minHeight: 213,
    width: 300,
    borderRadius: 16,
    backgroundColor: '#fff',
    paddingHorizontal: 24,
    paddingVertical: 16,
  },
  container: {
    // width: 300,
    borderRadius: 16,
    // height: 328,
    // backgroundColor: '#fff',
  },
  title: {
    fontSize: 18,
    fontWeight: '500',
    textAlign: 'center',
  },
  contentText: {
    fontSize: 16,
    marginTop: 20,
    lineHeight: 22,
  },
  maskBackgroundContent: {
    flex: 1,
  },
  maskCommon: {
    position: 'absolute',
    width: '100%',
    height: '100%',
    alignItems: 'center',
    justifyContent: 'center',
    backgroundColor: '#000',
  },
});

export function ImageModal({
  src,
  visible,
  onCancel,
}: {
  src: string;
  visible: boolean;
  onCancel: () => void;
}) {
  const { width, height } = Dimensions.get('screen');
  /** 封装url,自行替换掉 */
  const getMediaSource = useMediaSource();
  const widthAlpha = useSharedValue(width);
  const heightAlpha = useSharedValue(width);

  const curScale = useSharedValue(1);
  /** 初始化 */
  useEffect(() => {
    if (visible) {
      curScale.value = 1;
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [visible]);

  const initImgSize = ({
    nativeEvent,
  }: {
    nativeEvent: { width: number; height: number };
  }) => {
    const scale = nativeEvent.width / nativeEvent.height;
    heightAlpha.value = widthAlpha.value / scale;
  };

  const pinchGesture = Gesture.Pinch()
    .onUpdate((e) => {
      const { scale } = e;
      if (scale < 0.8) {
        return;
      }
      if (scale > 4) {
        return;
      }
      curScale.value = scale;
    })
    .onEnd((e) => {
      const { scale } = e;
      if (scale < 0.8) {
        curScale.value = withTiming(1);
      }
    });

  const animatedStyles = useAnimatedStyle(() => ({
    width: widthAlpha.value * curScale.value,
    height: heightAlpha.value * curScale.value,
  }));

  const pressGesture = Gesture.Tap().onStart(() => {
    runOnJS(onCancel)();
  });
  const composed = Gesture.Race(pinchGesture, pressGesture);

  return (
    <Overlay visible={visible} transparent>
      <GestureHandlerRootView style={{ flex: 1, backgroundColor: '#000' }}>
        <GestureDetector gesture={composed}>
          <View style={[{ width, height: height - 1 }, styles.overlay]}>
            <ScrollView
              style={[{ flex: 1 }]}
              bounces={false}
              horizontal
              contentContainerStyle={{
                alignItems: 'center',
                justifyContent: 'center',
              }}
            >
              <ScrollView
                style={{ flex: 1 }}
                bounces={false}
                contentContainerStyle={{
                  alignItems: 'center',
                  justifyContent: 'center',
                }}
              >
                <Animated.View style={[animatedStyles]}>
                  <FastImageWithDefault
                    source={getMediaSource(src)}
                    style={[{ width: '100%', height: '100%' }]}
                    onLoad={initImgSize}
                  />
                </Animated.View>
              </ScrollView>
            </ScrollView>
          </View>
        </GestureDetector>
      </GestureHandlerRootView>
    </Overlay>
  );
}

export default ImageModal;

踩坑记录

1、react-native-gesture-handler 在安卓上,手势不会触发

RNGH 直接使用在react-native的moadl上不会生效,查看了官方的issue以及相关的文档,发现确实有这个问题。RNGH官方给出的解决方案是

截屏2022-07-28 15.03.30.png 使用'gestureHandlerRootHOC'.

社区有建议使用react-navigation-modal使用。

但是我们并未使用react-native 的modal 组件,而是团队的overlay(protal组件)依然遇到这个问题。考虑到传送门放在最顶层,在navigation之外,可能是没用提供响应的视图。最后加入GestureHandlerRootView发现确实生效,解决了这个没有手势响应的问题

2、在小米机型上,手势包裹的View 组件一旦默认覆盖屏幕,在添加玩backgroundcolor 后,缩放后,图片被背景色覆盖,未知原因,这就是上面的view 高度减一的原因。原理不明,猜测是不同厂商兼容性问题。希望有大佬给出解答

最后

以上方案不是最终的缩放方案,还需要调整,以及解决那些兼容性问题,组件需要重写。前一次缩放的值也没有记录,导致每次手势需要重新开始计算。等更新完成后,会更改这篇文章。

以上,转载需要注明出处,谢谢