Skip to content
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

How about some features? #1

Open
Splicer97 opened this issue Feb 2, 2023 · 5 comments
Open

How about some features? #1

Splicer97 opened this issue Feb 2, 2023 · 5 comments

Comments

@Splicer97
Copy link

Splicer97 commented Feb 2, 2023

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

@andresribeiro
Copy link
Owner

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
Copy link

dodoto commented Feb 6, 2023

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
Copy link

talyosha commented Apr 12, 2023

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
Copy link

dodoto commented Apr 14, 2023

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;

@andresribeiro
Copy link
Owner

trying to work on this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants