andresribeiro / react-native-reanimated-image-viewer

A image viewer for React Native created with Reanimated
MIT License
139 stars 8 forks source link

How about some features? #1

Open Splicer97 opened 1 year ago

Splicer97 commented 1 year ago

Hi! How about array of images (imageUrls)? Something like react-native-image-viewing.

andresribeiro commented 1 year ago

This is definitely something I wanna add, but since it would involves the FlatList, the gestures would be a bit more complex, and I haven't studied how I would do this yet

dodoto commented 1 year ago

I think you can give FlatList or ScrollView scrollEnabled={false}, and give the listRef as props to ImageViewer. And then, you can scroll List by scrollTo (from reanimated) with gesture handler. List:

      <ScrollView
        ref={listRef}
        scrollEnabled={false}
        pagingEnabled
        horizontal
        showsHorizontalScrollIndicator={false}>
        {images.map((image, index) => (
          <ImageViewer
            listRef={listRef}
            index={index}
            {...image}
            key={index}
            maxIndex={images.length - 1}
            onDismiss={handleDismiss}
          />
        ))}
      </ScrollView>

ImageViewer:

onActive: () => {
  scrollTo(listRef, index * SCREEN_WIDTH - overOffsetX, 0, animated: false)
},
onEnd: () => {
        if (Math.abs(overOffsetX) >= HORIZONTAL_THRESHOLD) {
          overOffsetX > 0
            ? scrollTo(listRef, (index - 1) * SCREEN_WIDTH, 0, true)
            : scrollTo(listRef, (index + 1) * SCREEN_WIDTH, 0, true);
        } else {
          scrollTo(listRef, index * SCREEN_WIDTH, 0, true);
        }
}
talyosha commented 1 year ago

I think you can give FlatList or ScrollView scrollEnabled={false}, and give the listRef as props to ImageViewer. And then, you can scroll List by scrollTo (from reanimated) with gesture handler. List:

      <ScrollView
        ref={listRef}
        scrollEnabled={false}
        pagingEnabled
        horizontal
        showsHorizontalScrollIndicator={false}>
        {images.map((image, index) => (
          <ImageViewer
            listRef={listRef}
            index={index}
            {...image}
            key={index}
            maxIndex={images.length - 1}
            onDismiss={handleDismiss}
          />
        ))}
      </ScrollView>

ImageViewer:

onActive: () => {
  scrollTo(listRef, index * SCREEN_WIDTH - overOffsetX, 0, animated: false)
},
onEnd: () => {
        if (Math.abs(overOffsetX) >= HORIZONTAL_THRESHOLD) {
          overOffsetX > 0
            ? scrollTo(listRef, (index - 1) * SCREEN_WIDTH, 0, true)
            : scrollTo(listRef, (index + 1) * SCREEN_WIDTH, 0, true);
        } else {
          scrollTo(listRef, index * SCREEN_WIDTH, 0, true);
        }
}

@dodoto could you please explain in more detail the location of the code in ImageViewer?

dodoto commented 1 year ago

a demo code with pan and tap scale


import React, { FC, useEffect, useRef } from 'react';
import { Dimensions, Image, StyleSheet, ImageURISource, Text, ScrollView, FlatList } from 'react-native';
import Animated, { useAnimatedStyle, useSharedValue, useAnimatedGestureHandler, withTiming, scrollTo, useAnimatedRef } from 'react-native-reanimated';
import { PanGestureHandler, PanGestureHandlerGestureEvent, TapGestureHandler, TapGestureHandlerGestureEvent } from 'react-native-gesture-handler';

const source = require('../assets/masthead.png');

const { width, height } = Dimensions.get('window'); // if statusbar translucent, use Dimensions.get('screen') const center = { x: width / 2, y: height / 2 };

const getImageSize = (source: ImageURISource | number): Promise<{ width: number, height: number }> => { return new Promise((resolve,reject) => { if (typeof source === 'number') { const { width, height } = Image.resolveAssetSource(source) resolve({width, height}) } else { Image.getSize(source.uri!, (width, height) => { resolve({width, height}) },(error) => { reject(error) }) } }) }

const styles = StyleSheet.create({ root: { flex: 1 }, container: { width, height, position: "relative", justifyContent: "center", alignItems: 'center', backgroundColor: "black" }, image: { width,

}, title: { position: 'absolute', top: 0, left: 0, right: 0, textAlign: 'center', fontSize: 20, fontWeight: 'bold', color: 'white', }, });

type PanGestureHandlerGestureContext = { right: number; left: number; startTranslateX: number; overOffsetX: number; };

interface ImageViewerProps { index: number; title: string; listRef: React.RefObject<ScrollView | FlatList>; }

export const ImageViewer: FC = ({ index, title, listRef }) => { const imageSize = useSharedValue({ width, height: 0 }); const imageTranslate = useSharedValue({ x: 0, y: 0 }); const imageScale = useSharedValue(1);

const tap = useRef(); const pan = useRef();

const handleTap = useAnimatedGestureHandler({ onEnd() { const isScaled = imageScale.value === 2; imageScale.value = withTiming(isScaled ? 1 : 2); if (isScaled) { imageTranslate.value.x = withTiming(0); } } });

// only handle horizontal const handlePan = useAnimatedGestureHandler<PanGestureHandlerGestureEvent, PanGestureHandlerGestureContext>({ onStart(_event, context) { console.log('title', title); const leftBound = Math.min(center.x - (imageSize.value.width imageScale.value) / 2, 0); // ----> const rightBound = -leftBound; // <------ context.left = leftBound; context.right = rightBound; context.startTranslateX = imageTranslate.value.x; }, onActive(event, context) { const rawTranslate = event.translationX + context.startTranslateX; let translateX = rawTranslate; let overOffsetX = 0; // trigger list slide if (translateX > context.right) { translateX = context.right; overOffsetX = rawTranslate - context.right; console.log('slide to right'); } if (translateX < context.left) { translateX = context.left; overOffsetX = rawTranslate - context.left; console.log('slide to left'); } context.overOffsetX = overOffsetX; if (overOffsetX !== 0) { scrollTo(listRef, -overOffsetX + index width, 0, false); }

  const translateY = imageTranslate.value.y;
  imageTranslate.value = {
    x: translateX,
    y: translateY,
  };
},
onEnd(_event, context) {
  const shreshold = 100;
  let scrollX = index * width;
  if (context.overOffsetX >= shreshold) {
    scrollX = (index - 1) * width;
  }
  if (context.overOffsetX <= -shreshold) {
    scrollX = (index + 1) * width;
  }
  scrollTo(listRef, scrollX, 0, true);
},

});

const imageStyle = useAnimatedStyle(() => ({ width: imageSize.value.width, height: imageSize.value.height, transform: [ { translateX: imageTranslate.value.x }, { translateY: imageTranslate.value.y }, { scale: imageScale.value }, ], }))

useEffect(() => { getImageSize(source).then((size) => { imageSize.value = { width, height: width * size.height / size.width, }; }) }, []);

return (

{ title } ); }; const ImageList: FC = () => { const listRef = useAnimatedRef(); return ( ); } export default ImageList; ```