Как исправить положение ползунка сравнения изображений в ландшафтном режиме в React Native?

Вопрос или проблема

Я пытаюсь создать слайдер для сравнения изображений на React Native, как здесь https://www.npmjs.com/package/react-compare-slider. Я почти построил компонент в портретном режиме. Но, когда я пытался сделать то же самое в альбомном режиме, перетаскивание слайдера работает не так, как ожидалось.

Вот код

import React, {useState, useEffect} from 'react';
import {
  View,
  Image,
  PanResponder,
  Animated,
  StyleSheet,
  Dimensions,
  ActivityIndicator,
} from 'react-native';
import Orientation from 'react-native-orientation-locker';
// import {CompareSlider} from 'react-native-compare-slider';

const ImageComparisonSlider = ({
  leftImage,
  rightImage,
  initialPosition = 0.5,
}) => {
  const [sliderPosition, setSliderPosition] = useState(
    new Animated.Value(initialPosition * Dimensions.get('window').width),
  );
  const [leftImageLoaded, setLeftImageLoaded] = useState(false);
  const [rightImageLoaded, setRightImageLoaded] = useState(false);
  const [isDeviceRotated, setisDeviceRotated] = useState(false);

  const panResponder = PanResponder.create({
    onStartShouldSetPanResponder: () => true,
    onPanResponderMove: (_, gestureState) => {
      const newPosition = Math.max(
        0,
        Math.min(gestureState.moveX, Dimensions.get('window').width),
      );

      sliderPosition.setValue(newPosition);
    },
  });

  const renderImage = (imageUri, onLoadEnd, style) => (
    <Image
      source={{uri: imageUri}}
      style={style}
      resizeMode="cover"
      onLoadEnd={onLoadEnd}
    />
  );

  const bothImagesLoaded = leftImageLoaded && rightImageLoaded;

  const onOrientationDidChange = orientation => {
    if (orientation == 'LANDSCAPE-LEFT' || orientation == 'LANDSCAPE-RIGHT') {
      setisDeviceRotated(true);
    } else {
      setisDeviceRotated(false);
    }
  };

  useEffect(() => {
    Orientation.addOrientationListener(onOrientationDidChange);
    return () => Orientation.removeOrientationListener(onOrientationDidChange);
  }, []);

  return (
    <View
      style={{
        width: isDeviceRotated ? '50%' : '100%',
        height: !isDeviceRotated ? '50%' : '100%', // Настройте при необходимости
        position: 'relative',
      }}>
      {!bothImagesLoaded && (
        <View style={styles.loaderContainer}>
          <ActivityIndicator size="large" color="#0000ff" />
        </View>
      )}
      {bothImagesLoaded && (
        <>
          {renderImage(rightImage, () => {}, styles.image)}
          <Animated.View
            style={[
              styles.leftImageContainer,
              {
                width: sliderPosition,
              },
            ]}>
            {renderImage(leftImage, () => {}, styles.leftImage)}
          </Animated.View>
          <Animated.View
            style={[styles.sliderLine, {left: sliderPosition}]}
            {...panResponder.panHandlers}>
            <View style={styles.sliderHandle} />
          </Animated.View>
        </>
      )}
      <Image
        source={{uri: leftImage}}
        style={styles.hiddenImage}
        onLoad={() => setLeftImageLoaded(true)}
      />
      <Image
        source={{uri: rightImage}}
        style={styles.hiddenImage}
        onLoad={() => setRightImageLoaded(true)}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  image: {
    width: '100%',
    height: '100%',
  },
  leftImageContainer: {
    position: 'absolute',
    top: 0,
    left: 0,
    bottom: 0,
    overflow: 'hidden',
  },
  leftImage: {
    width: Dimensions.get('window').width,
    height: '100%',
  },
  sliderLine: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    width: 3,
    backgroundColor: 'white',
  },
  sliderHandle: {
    position: 'absolute',
    top: '50%',
    left: -10,
    width: 20,
    height: 20,
    borderRadius: 10,
    backgroundColor: 'white',
    transform: [{translateY: -10}],
  },
  loaderContainer: {
    ...StyleSheet.absoluteFillObject,
    justifyContent: 'center',
    alignItems: 'center',
  },
  hiddenImage: {
    width: 0,
    height: 0,
  },
});

export default ImageComparisonSlider;


Пожалуйста, посмотрите это также: https://www.loom.com/share/4dc8d887fe1449268c8c944aa25f6dbd

Будет очень полезно, если я найду решение, и, пожалуйста, дайте знать, если есть другие пакеты, которые могут решить эту проблему

Заранее спасибо!

Вам, вероятно, нужно сбросить ширину окна, когда экран меняет ориентацию. Похоже, что у вас есть обновление состояния, но ширина окна инициализируется, но не обновляется, если вы переключаете ориентацию.

const [windowWidth, setWindowWidth] = useState(Dimensions.get("window").width);

const onOrientationDidChange = orientation => {
  if (orientation == 'LANDSCAPE-LEFT' || orientation == 'LANDSCAPE-RIGHT') {
    setisDeviceRotated(true);
  } else {
    setisDeviceRotated(false);
  }
  setWindowWidth(Dimensions.get("window").width);
};

Вы также можете попробовать использовать хук useWindowDimensions, например, const width = useWindowDimensions().width;

Ответ или решение

Для решения проблемы с некорректной работой ползунка сравнения изображений в ландшафтном режиме в React Native, важно учитывать изменение ширины экрана при изменении ориентации устройства. Ваш код уже имеет логику определения ориентации, но ширина окна и положение ползунка не обновляются соответствующим образом.

  1. Используйте useWindowDimensions: Это позволит вам автоматически отслеживать изменения ширины и высоты окна. С помощью этого хука вам не нужно будет вручную обновлять ширину при изменении ориентации.

  2. Обновите состояние sliderPosition на основе новой ширины: При каждом изменении ориентации, обновляйте значения, чтобы гарантировать, что ползунок остается корректно позиционированным.

Ниже приведен обновленный код с учетом этих изменений:

import React, { useState, useEffect } from 'react';
import {
  View,
  Image,
  PanResponder,
  Animated,
  StyleSheet,
  ActivityIndicator,
  useWindowDimensions,
} from 'react-native';
import Orientation from 'react-native-orientation-locker';

const ImageComparisonSlider = ({
  leftImage,
  rightImage,
  initialPosition = 0.5,
}) => {
  const { width } = useWindowDimensions(); // Получаем ширину окна через хук
  const [sliderPosition, setSliderPosition] = useState(
    new Animated.Value(initialPosition * width)
  );
  const [leftImageLoaded, setLeftImageLoaded] = useState(false);
  const [rightImageLoaded, setRightImageLoaded] = useState(false);

  const panResponder = PanResponder.create({
    onStartShouldSetPanResponder: () => true,
    onPanResponderMove: (_, gestureState) => {
      const newPosition = Math.max(
        0,
        Math.min(gestureState.moveX, width)
      );
      sliderPosition.setValue(newPosition);
    },
  });

  const renderImage = (imageUri, onLoadEnd, style) => (
    <Image
      source={{ uri: imageUri }}
      style={style}
      resizeMode="cover"
      onLoadEnd={onLoadEnd}
    />
  );

  const bothImagesLoaded = leftImageLoaded && rightImageLoaded;

  useEffect(() => {
    const updateSliderPosition = () => {
      setSliderPosition(new Animated.Value(initialPosition * width)); // Сброс значения ползунка
    };

    const subscription = Orientation.addOrientationListener(updateSliderPosition);

    return () => {
      subscription.remove();
    };
  }, [width]); // Обновляем при изменении ширины

  return (
    <View style={{ width: '100%', height: '100%', position: 'relative' }}>
      {!bothImagesLoaded && (
        <View style={styles.loaderContainer}>
          <ActivityIndicator size="large" color="#0000ff" />
        </View>
      )}
      {bothImagesLoaded && (
        <>
          {renderImage(rightImage, () => {}, styles.image)}
          <Animated.View
            style={[
              styles.leftImageContainer,
              {
                width: sliderPosition,
              },
            ]}
          >
            {renderImage(leftImage, () => {}, styles.leftImage)}
          </Animated.View>
          <Animated.View
            style={[styles.sliderLine, { left: sliderPosition }]}
            {...panResponder.panHandlers}
          >
            <View style={styles.sliderHandle} />
          </Animated.View>
        </>
      )}
      <Image
        source={{ uri: leftImage }}
        style={styles.hiddenImage}
        onLoad={() => setLeftImageLoaded(true)}
      />
      <Image
        source={{ uri: rightImage }}
        style={styles.hiddenImage}
        onLoad={() => setRightImageLoaded(true)}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  image: {
    width: '100%',
    height: '100%',
  },
  leftImageContainer: {
    position: 'absolute',
    top: 0,
    left: 0,
    bottom: 0,
    overflow: 'hidden',
  },
  leftImage: {
    width: '100%',
    height: '100%',
  },
  sliderLine: {
    position: 'absolute',
    top: 0,
    bottom: 0,
    width: 3,
    backgroundColor: 'white',
  },
  sliderHandle: {
    position: 'absolute',
    top: '50%',
    left: -10,
    width: 20,
    height: 20,
    borderRadius: 10,
    backgroundColor: 'white',
    transform: [{ translateY: -10 }],
  },
  loaderContainer: {
    ...StyleSheet.absoluteFillObject,
    justifyContent: 'center',
    alignItems: 'center',
  },
  hiddenImage: {
    width: 0,
    height: 0,
  },
});

export default ImageComparisonSlider;

Объяснение изменений:

  • Использован хук useWindowDimensions, который автоматически обновляет ширину окна.
  • Удалена лишняя логика, связанная с отслеживанием изменений ориентации, поскольку теперь ширина окна всегда актуальна.
  • Убедитесь, что вы обновляете sliderPosition при каждом изменении ширины, используя Animated.Value.

С помощью этих изменений ваш ползунок для сравнения изображений должен корректно функционировать в обоих режимах.

Если у вас есть дополнительные вопросы или вам нужна помощь с другим пакетом, не стесняйтесь спрашивать!

Оцените материал
Добавить комментарий

Капча загружается...