react-native-reanimated-image-viewer icon indicating copy to clipboard operation
react-native-reanimated-image-viewer copied to clipboard

How about some features?

Open Splicer97 opened this issue 2 years ago • 5 comments

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

Splicer97 avatar Feb 02 '23 20:02 Splicer97

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

andresribeiro avatar Feb 02 '23 21:02 andresribeiro

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 avatar Feb 06 '23 02:02 dodoto

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?

talyosha avatar Apr 12 '23 11:04 talyosha

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<ImageViewerProps> = ({ 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<TapGestureHandlerGestureEvent>({
    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 (
    <Animated.View style={styles.container}>
      <TapGestureHandler ref={tap} simultaneousHandlers={[pan]} maxDist={4} onGestureEvent={handleTap}>
        <Animated.View>
          <PanGestureHandler ref={pan} simultaneousHandlers={[tap]} onGestureEvent={handlePan}>
            <Animated.Image 
              source={source} 
              resizeMode="stretch"
              style={imageStyle}/>
          </PanGestureHandler>
        </Animated.View>
      </TapGestureHandler>
      <Text style={styles.title}>{ title }</Text>
    </Animated.View>
  );
};

const ImageList: FC = () => {
  const listRef = useAnimatedRef<ScrollView>();

  return (
    <ScrollView 
      ref={listRef}
      style={styles.root}
      scrollEnabled={false} 
      horizontal>
      <ImageViewer title="1" listRef={listRef} index={0}/>
      <ImageViewer title="2" listRef={listRef} index={1}/>
    </ScrollView>
  );
}

export default ImageList;

dodoto avatar Apr 14 '23 03:04 dodoto