基于手势的气泡网格的居中问题

问题描述 投票:0回答:1

我正在开发一个 React Native 应用程序,它带有一个可以平移和居中的气泡网格。我正在使用react-native-gesture-handler和react-native-reanimated,但我在居中逻辑方面遇到了问题。

我正在开发一个带有可平移气泡网格的 React Native 应用程序。平移后,我尝试选择最靠近屏幕中心的气泡,但我的 findMostCenteredBubble 函数未按预期工作。

findMostCenteredBubble 函数选择视觉上远离中心的气泡,而不是屏幕上最中心的气泡。

import React, {useState, useRef, useEffect} from 'react';
import {
  View,
  TouchableOpacity,
  Text,
  StyleSheet,
  Dimensions,
} from 'react-native';
import Animated, {
  ZoomIn,
  useSharedValue,
  useAnimatedStyle,
  withSpring,
  runOnJS,
} from 'react-native-reanimated';
import {
  GestureHandlerRootView,
  GestureDetector,
  Gesture,
} from 'react-native-gesture-handler';

const bubbles = Array.from({length: 60}, (_, i) => ({
  id: i + 1,
  label: `Bubble ${i + 1}`,
}));

interface BubblePosition {
  id: number;
  x: number;
  y: number;
}

const BUBBLE_SIZE = 80;
const BUBBLE_MARGIN = 10;
const BUBBLES_PER_ROW = 6;

const {width: SCREEN_WIDTH, height: SCREEN_HEIGHT} = Dimensions.get('window');

const BubbleEffect = () => {
  const [selectedBubble, setSelectedBubble] = useState<number | null>(null);
  const translateX = useSharedValue(0);
  const translateY = useSharedValue(0);
  const offsetX = useSharedValue(0);
  const offsetY = useSharedValue(0);

  const totalWidth = BUBBLES_PER_ROW * (BUBBLE_SIZE + BUBBLE_MARGIN * 2);
  const totalHeight =
    Math.ceil(bubbles.length / BUBBLES_PER_ROW) *
    (BUBBLE_SIZE + BUBBLE_MARGIN * 2);

  const initialOffsetX = (SCREEN_WIDTH - totalWidth) / 2;
  const initialOffsetY = (SCREEN_HEIGHT - totalHeight) / 2;

  console.log('Screen Dimensions:', {SCREEN_WIDTH, SCREEN_HEIGHT});
  console.log('Grid Dimensions:', {totalWidth, totalHeight});
  console.log('Initial Offset:', {initialOffsetX, initialOffsetY});

  const bubblePositions: BubblePosition[] = bubbles.map((_, index) => ({
    id: index + 1,
    x: (index % BUBBLES_PER_ROW) * (BUBBLE_SIZE + BUBBLE_MARGIN * 2),
    y: Math.floor(index / BUBBLES_PER_ROW) * (BUBBLE_SIZE + BUBBLE_MARGIN * 2),
  }));

  const handleBubblePress = (id: number) => {
    setSelectedBubble(id);
    centerBubble(id);
  };

  const findMostCenteredBubble = () => {
    'worklet';
    const centerX = SCREEN_WIDTH / 2;
    const centerY = SCREEN_HEIGHT / 2;

    console.log('Screen Center:', {centerX, centerY});
    console.log('Current Translation:', {
      x: translateX.value,
      y: translateY.value,
    });

    return bubblePositions.reduce(
      (closest, bubble) => {
        const bubbleCenterX =
          bubble.x + translateX.value + BUBBLE_SIZE / 2 + initialOffsetX;
        const bubbleCenterY =
          bubble.y + translateY.value + BUBBLE_SIZE / 2 + initialOffsetY;

        const distance = Math.sqrt(
          Math.pow(bubbleCenterX - centerX, 2) +
            Math.pow(bubbleCenterY - centerY, 2),
        );

        console.log(`Bubble ${bubble.id}:`, {
          centerX: bubbleCenterX,
          centerY: bubbleCenterY,
          distance,
        });

        if (distance < closest.distance) {
          return {id: bubble.id, distance};
        }
        return closest;
      },
      {id: -1, distance: Infinity},
    );
  };

  const centerBubble = (bubbleId: number) => {
    'worklet';
    const bubble = bubblePositions.find(b => b.id === bubbleId);
    if (bubble) {
      const targetX =
        SCREEN_WIDTH / 2 - (bubble.x + BUBBLE_SIZE / 2 + initialOffsetX);
      const targetY =
        SCREEN_HEIGHT / 2 - (bubble.y + BUBBLE_SIZE / 2 + initialOffsetY);

      console.log('Centering Bubble:', {id: bubbleId, targetX, targetY});

      translateX.value = withSpring(targetX);
      translateY.value = withSpring(targetY);
    }
  };

  const panGesture = Gesture.Pan()
    .onStart(() => {
      offsetX.value = translateX.value;
      offsetY.value = translateY.value;
    })
    .onUpdate(event => {
      translateX.value = offsetX.value + event.translationX;
      translateY.value = offsetY.value + event.translationY;
    })
    .onEnd(() => {
      console.log('Pan Gesture Ended');
      const mostCentered = findMostCenteredBubble();
      if (mostCentered.id !== -1) {
        console.log('Most Centered Bubble:', mostCentered);
        centerBubble(mostCentered.id);
        runOnJS(setSelectedBubble)(mostCentered.id);
      }
    });

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

  // Initial centering
  useEffect(() => {
    const initialCenterId = Math.ceil(bubbles.length / 2);
    console.log('Initial Centering:', initialCenterId);
    centerBubble(initialCenterId);
    setSelectedBubble(initialCenterId);
  }, []);

  return (
    <GestureHandlerRootView style={styles.safeArea}>
      <GestureDetector gesture={panGesture}>
        <Animated.View style={[styles.scrollContainer, animatedStyle]}>
          <View
            style={[
              styles.container,
              {
                width: totalWidth,
                height: totalHeight,
                left: initialOffsetX,
                top: initialOffsetY,
              },
            ]}>
            {bubblePositions.map(bubble => (
              <TouchableOpacity
                key={bubble.id}
                onPress={() => handleBubblePress(bubble.id)}
                activeOpacity={0.7}
                style={[
                  styles.bubbleWrapper,
                  {
                    left: bubble.x,
                    top: bubble.y,
                  },
                ]}>
                <Animated.View
                  entering={ZoomIn.duration(300)}
                  style={[
                    styles.bubble,
                    selectedBubble === bubble.id && styles.selectedBubble,
                  ]}>
                  <Text style={styles.label}>
                    {bubbles[bubble.id - 1].label}
                  </Text>
                </Animated.View>
              </TouchableOpacity>
            ))}
          </View>
        </Animated.View>
      </GestureDetector>
    </GestureHandlerRootView>
  );
};

const styles = StyleSheet.create({
  safeArea: {
    flex: 1,
    backgroundColor: '#fff',
  },
  scrollContainer: {
    flex: 1,
  },
  container: {
    position: 'absolute',
  },
  bubbleWrapper: {
    position: 'absolute',
    width: BUBBLE_SIZE + BUBBLE_MARGIN * 2,
    height: BUBBLE_SIZE + BUBBLE_MARGIN * 2,
    justifyContent: 'center',
    alignItems: 'center',
  },
  bubble: {
    width: BUBBLE_SIZE,
    height: BUBBLE_SIZE,
    borderRadius: BUBBLE_SIZE / 2,
    backgroundColor: '#D3D3D3',
    justifyContent: 'center',
    alignItems: 'center',
    shadowColor: '#000',
    shadowOpacity: 0.2,
    shadowRadius: 5,
    shadowOffset: {width: 2, height: 2},
  },
  selectedBubble: {
    backgroundColor: '#87CEEB',
  },
  label: {
    color: '#000',
    fontSize: 14,
    fontWeight: 'bold',
  },
});

export default BubbleEffect;
javascript reactjs react-native react-native-gesture-handler
1个回答
0
投票

问题的出现是因为在 findMostCenteredBubble 函数中两次包含了initialOffsetX和initialOffsetY。它们已计入translateX 和translateY 中。 计算bubbleCenterX和bubbleCenterY时只需删除initialOffsetX和initialOffsetY即可。

© www.soinside.com 2019 - 2024. All rights reserved.