应用需求,需要实现类似微信的图片缩放功能。鼓捣了一下,踩了点坑,最后还是感觉有问题,不过还是分享一下代码。希望老哥们斧正。
创作不易,点个赞就是对作者最大支持
首先贴下实现效果
以下为用到的插件
/** 动画插件 */
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官方给出的解决方案是
使用
'gestureHandlerRootHOC'.
社区有建议使用react-navigation-modal使用。
但是我们并未使用react-native 的modal 组件,而是团队的overlay(protal组件)依然遇到这个问题。考虑到传送门放在最顶层,在navigation之外,可能是没用提供响应的视图。最后加入GestureHandlerRootView发现确实生效,解决了这个没有手势响应的问题
2、在小米机型上,手势包裹的View 组件一旦默认覆盖屏幕,在添加玩backgroundcolor 后,缩放后,图片被背景色覆盖,未知原因,这就是上面的view 高度减一的原因。原理不明,猜测是不同厂商兼容性问题。希望有大佬给出解答
最后
以上方案不是最终的缩放方案,还需要调整,以及解决那些兼容性问题,组件需要重写。前一次缩放的值也没有记录,导致每次手势需要重新开始计算。等更新完成后,会更改这篇文章。
以上,转载需要注明出处,谢谢