Как попасть на изображение, которое пользователь щелкнул при смахивании изображения?

Приложение My React Native 0.64 использует обработчик жестов response-native-gesture-handler 1.16 / react-native-reanimated 2.1 для смахивания изображений влево и вправо. Идея состоит в том, что когда пользователь щелкает изображение в галерее изображений, изображение отображается на весь экран, а остальные изображения в галерее можно просматривать одно за другим, проводя пальцем влево или вправо. Вот полный код, который работает, но с одной уловкой: изображение, по которому щелкнули, не отображается, но первое изображение в галерее всегда отображается первым. Как попасть на изображение, по которому щелкнул пользователь?

      import React, { useRef } from "react";
import { Dimensions, StyleSheet, View, Platform } from "react-native";
import FastImage from 'react-native-fast-image';
import Animated, {
  useAnimatedStyle,
  useSharedValue,
  useAnimatedRef,
  useAnimatedGestureHandler,
  useAnimatedReaction,
  withTiming,
  withSpring,
  useDerivedValue,
} from "react-native-reanimated";
import {
  snapPoint,
} from "react-native-redash";
import {
  PanGestureHandler,
  PinchGestureHandler,
  State,
  TouchableOpacity,
} from "react-native-gesture-handler";

const { width, height } = Dimensions.get("window");

export default Swipe = ({route, navigation}) => {
    const {images, initIndex} = route.params;  //initIndex is the index of image clicked
    console.log("route params swipe ", route.params);
    const index = useSharedValue(initIndex);
    const snapPoints = images.map((_, i) => i * -width);
    const panRef = useAnimatedRef();
    const pinchRef = useAnimatedRef();
    const offsetX = initIndex ? useSharedValue(initIndex * -width) : useSharedValue(0);  //The init offsetX is set according to initIndex a user clicked.
    const translationX = useSharedValue(0);
    const translateX = useSharedValue(0); //if translateX was assgined with offsetX, then swipe stops working
    //for snap in swipe
    const lowVal = useDerivedValue(() => {return (index.value + 1) * -width}); //shared value, with .value
    const highVal = useDerivedValue(() => {return (index.value - 1) * -width});  //shared value, with .value
    const snapPt = (e) => {
        "worklet"; 
        return snapPoint(translateX.value, e.velocityX, snapPoints);
    };
    
    const panHandler = useAnimatedGestureHandler(
        {
         onStart: () => {
            translateX.value = offsetX.value;  //As soon as a user swipe, this line allow the 
 //image is moved to initIndex-th image. But how to land on the image a user has clicked?
         },
         onActive: (event) => {
            translateX.value = offsetX.value + event.translationX;
            translationX.value = event.translationX;
         },
         onEnd: (event) => {
            let val = snapPt(event);
            let sp = Math.min(Math.max(lowVal.value, val), highVal.value);  //clamp in RN redash
            if (event.translationX !== 0) {
                translateX.value = withTiming(sp);
                offsetX.value = translateX.value;
            };
            //update index
            index.value = Math.floor(translateX.value/-width);          
         }, 
        }, []
    )

    const swipeStyle = useAnimatedStyle(() => {
        return {
            transform: [{ translateX: translateX.value}]
        }
    });

    return (
        <PanGestureHandler
            onGestureEvent={panHandler}
            ref={panRef}
            minDist={10}
            avgTouches
        >
        <Animated.View style={styles.container}>
                <Animated.View
                style={[{width: width * images.length, height, flexDirection: "row"}, swipeStyle]}
                >
                  
                {images.map((img_source, i) => {
                    const isActive = useDerivedValue(() => {return index.value === i});
                    
                    return (
                    <View key={i} style={styles.picture}>
                        <View style={[
                            styles.image,
                        ]}>
                          <TouchableOpacity onPress={() => navigation.goBack()} style={styles.button}>
                            <View style={styles.button}>
                              <FastImage 
                                          source={{uri:img_source.path}} 
                                          resizeMode={FastImage.resizeMode.contain} 
                                          style={styles.image}
                              />
                            </View>
                          </TouchableOpacity>
                        </View>
                    </View>
                    );
                })}
                
                </Animated.View>
            </Animated.View>
            </PanGestureHandler>    
    );
}

const styles = StyleSheet.create({
container: {
  ...StyleSheet.absoluteFillObject,
  backgroundColor: "black",
},

picture: {
  width,
  height,
  overflow: "hidden",
},
image: {
  //...StyleSheet.absoluteFillObject,
  flex:1,
  width: undefined,
  height: undefined,
  //resizeMode: "cover",
},
button: {
  width: width,
  height:height,
},

});

Еще одна проблема с кодом заключается в том, что при касании изображения перед смахиванием изображение дергается влево или вправо на 1/3-1/2 ширины экрана. Это делает смахивание не плавным. Хотелось бы знать, как устранить рывки изображения и сделать смахивание плавным от начала до конца.

1 ответ

Чтобы попасть на изображение, по которому щелкнул пользователь, добавляется хук useEffect:

      useEffect(() => {
        translateX.value = offsetX.value;
    }, [initIndex]);

В onStart необходимо удалить полностью.

Причина подергивания изображения вызвана искажением значения. Должен быть на границе экрана (кратной ширине экрана). Однако, когда ширина изображения меньше ширины экрана, изображение может быть на границе изображения, которое немного меньше или больше (в зависимости от того, проведите ли пальцем влево или вправо), и это вызывает рывки изображения влево или вправо при первоначальном проведении. . В этом случае offsetX должен быть установлен на границе экрана (кратной ширине экрана).