-
Notifications
You must be signed in to change notification settings - Fork 11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[Question] Using ResumableZoom inside a ScrollView, how to disable scroll when pinching? #68
Comments
Hello there, let's start for the most basic question, why are using ResumableZoom instead of the already built-in Gallery? |
It's an ongoing project that already has a carousel gallery. I tried to negotiate to change the carousel lib but didn't have success. Because of this, I'm trying to make things work together. Gallery Componentfunction Gallery({ index, photos, onLoadEnd, gallerySync }) {
const [currentIndex, setCurrentIndex] = useState(index);
const [isZoomEnabled, setZoomEnabled] = useState(false);
const resumableZoomRef = useRef<ResumableZoomType[]>([]);
const timeoutID = useRef<NodeJS.Timeout>();
const navigation = useNavigation<StackNavigationProp<ParamListBase>>();
const { width } = useWindowDimensions();
const { isLandscapeOrientation } = useDeviceOrientation();
function setGalleryIndex(newIndex: number) {
if (currentIndex !== newIndex) {
resumableZoomRef.current[currentIndex].reset(true);
}
gallerySync?.(newIndex);
setCurrentIndex(newIndex);
}
function onResizeForIndex(itemIndex: number) {
return function onResize(scale: number) {
if (itemIndex !== currentIndex) return;
if (timeoutID.current) {
clearTimeout(timeoutID.current);
timeoutID.current = undefined;
}
timeoutID.current = setTimeout(() => {
setZoomEnabled(scale !== 1);
}, 500);
};
}
const onPanEnd = useCallback((item: PhotoItem) => {
tracker.trackEvent('partner_gallery_photo_scrolled', {
photo_position: index,
photo_id: item.id,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
const onPinchEnd = useCallback((scale: number, item: PhotoItem) => {
const event = scale > 1 ? 'zoom_in' : 'zoom_out';
tracker.trackEvent(`partner_gallery_photo_${event}`, {
photo_position: index,
photo_id: item.id,
});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
// This is the component that uses ResumableZoom
function renderItem({ item, index: itemIndex }) {
return (
<Photo
item={item}
panEnabled={isZoomEnabled}
pinchEnabled={enablePartnerGalleryZoom}
tapsEnabled={enablePartnerGalleryZoom}
onLoadEnd={onLoadEnd}
onPanEnd={() => onPanEnd(item)}
onPinchEnd={e => onPinchEnd(e.scale, item)}
onZoomUpdate={onResizeForIndex(itemIndex)}
ref={el => {
if (el) resumableZoomRef.current[itemIndex] = el;
}}
/>
);
}
const style = StyleSheet.absoluteFillObject;
return (
<Box alignItems="center" justifyContent="center" style={style}>
<SnapCarousel
enableSnap
enableMomentum
horizontal
useScrollView
scrollEnabled={!isZoomEnabled}
initialNumToRender={1}
maxToRenderPerBatch={2}
sliderWidth={width}
itemWidth={width}
data={photos}
firstItem={currentIndex}
keyExtractor={(item: PhotoItem) => item.id}
renderItem={renderItem}
onBeforeSnapToItem={setGalleryIndex}
onSnapToItem={setGalleryIndex}
/>
{photos.length > 1 && (
<Dots length={photos.length} current={currentIndex} />
)}
</Box>
);
} Photo Componentexport const Photo = forwardRef<ResumableZoomType, PhotoProps>(
(
{ item, panEnabled = false, onLoadEnd, onZoomUpdate, ...zoomProps },
ref,
) => {
const resumableZoomRef = useRef<ResumableZoomType>(null);
useImperativeHandle<Partial<ResumableZoomType>, Partial<ResumableZoomType>>(
ref,
() => ({
reset: (...args) => resumableZoomRef.current?.reset(...args),
}),
[],
);
const { isLandscapeOrientation } = useDeviceOrientation();
const { height, width } = useWindowDimensions();
const { resolution, isFetching } = useImageResolution({ uri: item.uri });
function onUpdateHandler({ scale }: CommonZoomState<number>) {
'worklet';
runOnJS(onZoomUpdate)(scale); // Used to listen to zoom changes
}
if (isFetching || resolution === undefined) {
return (
<Loading
testID="gallery-photo-loading"
hardwareAccelerationAndroid
autoPlay
loop
/>
);
}
const imageStyle = getAspectRatioSize({
aspectRatio: resolution.width / resolution.height,
width: isLandscapeOrientation() ? undefined : width, // Set width and height based on device orientation
height: isLandscapeOrientation() ? height : undefined,
});
return (
<Box flex={1} testID="gallery-image-container">
<ResumableZoom
extendGestures
panEnabled={panEnabled}
maxScale={3}
scaleMode="clamp"
onUpdate={onUpdateHandler}
ref={resumableZoomRef}
{...zoomProps}>
<Image
accessibilityRole="image"
resizeMethod="scale"
onLoadEnd={onLoadEnd}
source={{ uri: item.uri }}
style={imageStyle}
/>
</ResumableZoom>
</Box>
);
},
); SnapCarousel uses ScrollView with horizontal scroll and snap enabled, but I can't pass an android-gesture-conflict.mp4I tried wrapping SnapCarousel with a GestureDetector and configuring a way to disable pinch only on ScrollView. I also tried negating ScrollView to be the move responder (probably incorrectly). I tried adding None of this works. I don't have ideas to fix this issue. |
The lib you guys are using does not let any room for a work around, gestures and scroll are by nature conflicting behaviors and whichever triggers first negates the other. Talking of My best advise is to negotiate again and let know however takes the final decision that you can have scroll or zoom but not both as the zoom capabilities need to be tightly integrated with the scroll-able component itself in such a way they do not collide with each other. |
Thanks. That was my suspicion and this library is no longer maintained but has been used in the app for a while. I found another one that uses gesture-handler and reanimated instead of ScrollView. |
If you're speaking of Talking of the gallery shipped with the library this one does not require ResumableZoom, all gestures are already part of the gallery so you just need to pass your images (Not wrapped by any zoom component), all your items share a single instance of ResumableZoom this made for performance reasons, when I mean zoom and scroll need to be tightly integrated this is what I meant because the zoom component needs to drive the scrolling. The only problem I think you may encounter is the orientation thing, so try it out and let me know. |
Hello Again! As I comment on another issue I'm using react-native-snap-carousel to develop a photo gallery with zoom behavior.
SnapCarousel uses ScrollView with horizontal scroll and snap enabled. Each item of the carousel is a ResumableZoom with pinch and double tap enabled with 4x zoom scale.
On Android, when I start a pinch gesture, moving two fingers together, works properly. But if I move only 1 finger even if I have 2 touch points, the pinch is not detected and ScrollView starts scrolling to the next or previous photo depending on the direction of the moving finger.
I try to convert the SnapCarousel to an animated component but can't find a way to make it work.
Is there any advice that you can give me in this situation?
The text was updated successfully, but these errors were encountered: