我正在尝试在 React Native 中使用 PanResponder 实现元素的对角线选择

I am trying to achieve diagonal selection of elements with PanResponder in React Native

我正在尝试通过 PanResponder 实现对数组的多个元素的选择。它适用于水平或垂直触摸序列,但我无法使其适用于对角线。当我说对角线时,它会选择数组中被触摸元素旁边的所有元素,但我只想保留被触摸的元素。例如我只需要 1、7 和 13。我怎样才能做到这一点?

代码如下

import React, {useState, useEffect, useRef} from 'react';
import {
  View,Dimensions,
  Text,
  StyleSheet,
  TouchableOpacity,
  PanResponder,
  PanResponderGestureState,
  SafeAreaView,
  LayoutChangeEvent,
} from 'react-native';

const SQUARE_SIZE = Dimensions.get("window").width/5;

const squareList = [
  1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
  23, 24, 25, 26, 27,
];

type OffsetType = {
  id: number;
  x: number;
  y: number;
  height: number;
  width: number;
};

export default function App() {
  const [selectedList, setSelectedList] = useState([]);
  const [gestureSelectionList, setGestureSelectionList] = useState(
    [],
  );
  const [offset, setOffset] = React.useState([]);
  const [translate, setTranslate] = useState(
    null,
  );

  console.log(
    'offset',
    offset.find(item => item.id === 2),
  );

  useEffect(() => { 
    console.log("ASDSA");
    if (translate !== null) {
      offset.map(offsetItem => {
        const {moveX, moveY, x0, y0} = translate;
        if (
          (offsetItem.x >= x0 - SQUARE_SIZE &&
          offsetItem.y >= y0 - SQUARE_SIZE &&
          offsetItem.x <= moveX &&
          offsetItem.y <= moveY)
        ) {
          const isAlreadySelected = gestureSelectionList.find(
            item => item === offsetItem.id,
          );
          if (!isAlreadySelected) {
            setGestureSelectionList(prevState => [...prevState, offsetItem.id]);
          }
        } else {
          const isAlreadySelected = gestureSelectionList.find(
            item => item === offsetItem.id,
          );
          if (isAlreadySelected) {
            const filterSelectedItem = gestureSelectionList.filter(
              item => item !== offsetItem.id,
            );
            setGestureSelectionList(filterSelectedItem);
          }
        }
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [translate]);

  const onSelectItem = (pressedItem: number) => {
    const isAlreadySelected = selectedList.find(item => item === pressedItem);
    if (isAlreadySelected) {
      const filterSelectedItem = selectedList.filter(
        item => item !== pressedItem,
      );
      setSelectedList(filterSelectedItem);
    } else {
      setSelectedList(prevState => [...prevState, pressedItem]);
    }
  };

  function removeDuplicateAndMerge(
    selectedArray: number[],
    gestureSelectedArray: number[],
  ): number[] {
    const myArray = selectedArray.filter(function (el) {
      return gestureSelectedArray.indexOf(el) < 0;
    });
    const revArray = gestureSelectedArray.filter(function (el) {
      return selectedArray.indexOf(el) < 0;
    });

    return [...myArray, ...revArray];
  }

  useEffect(() => {
    if (!translate) {
      setSelectedList(
        removeDuplicateAndMerge(selectedList, gestureSelectionList),
      );
      setGestureSelectionList([]);
    }
  }, [translate]);

  const panResponder = useRef(
    PanResponder.create({
      onMoveShouldSetPanResponder: () => true,
      onMoveShouldSetPanResponderCapture: _evt => true,
      onPanResponderMove: (_evt, gesture) => {
        setTranslate({...gesture});
      },
      onPanResponderRelease: () => {
        setTranslate(null);
      },
      onShouldBlockNativeResponder: () => true,
    }),
  ).current;

  const itemStyle = (item: number) => {
    const gestureBGColor = gestureSelectionList.find(
      selectedItem => selectedItem === item,
    )
      ? true
      : false;
    const selectedBGColor = selectedList.find(
      selectedItem => selectedItem === item,
    )
      ? true
      : false;
    return {
      backgroundColor: gestureBGColor
        ? 'gray'
        : selectedBGColor
        ? 'blue'
        : 'orangered',
    };
  };

  return (
    <View style={styles.listWrapper} {...panResponder.panHandlers}>
        {squareList.map(item => {
          return (
            <TouchableOpacity
              onLayout={(event: LayoutChangeEvent | any) => {
                event.target.measure(
                  (
                    _x: number,
                    _y: number,
                    width: number,
                    height: number,
                    pageX: number,
                    pageY: number,
                  ) => {
                    setOffset(prevOffset => [
                      ...prevOffset,
                      {
                        id: item,
                        x: pageX,
                        y: pageY,
                        width,
                        height,
                      },
                    ]);
                  },
                );
              }}
              onPress={() => onSelectItem(item)}
              key={item}
              style={[styles.squareStyle, itemStyle(item)]}>
              <Text style={{color: '#fff', fontSize: 18}}>{item}</Text>
            </TouchableOpacity>
          );
        })}
      </View>
  );
}

const styles = StyleSheet.create({
  listWrapper: {
    flexDirection: 'row',
    flexWrap: 'wrap',
  },
  squareStyle: {
    backgroundColor: 'orangered',
    height: SQUARE_SIZE,
    width: SQUARE_SIZE,
    borderWidth:1,
    justifyContent: 'center',
    alignItems: 'center',
  },
});

您检查触摸移动是否在特定矩形内的计算不正确。您需要将其更改为以下内容。

const {moveX, moveY, x0, y0} = translate;

if (moveX > offsetItem.x && moveX < offsetItem.x + SQUARE_SIZE && moveY > offsetItem.y && moveY < offsetItem.y + SQUARE_SIZE) {
   ...
}

这也解决了您在问题中没有说明的问题:如果您从下到上开始,您的选择不起作用。使用上面的方法也可以解决这个问题。

您也不需要更新选择列表的 else 部分。请注意,由于对角线非常细,因此有可能(如果您没有非常精确地触摸)您触摸旁边的项目也被选中)。我增加了一些容忍度来解决这个问题。你可能会稍微调整一下。正确的 useEffect 实现如下所示。

useEffect(() => { 
    if (translate !== null) {
      offset.map(offsetItem => {
        const {moveX, moveY, x0, y0} = translate;
        if (moveX > offsetItem.x + 5 && moveX < offsetItem.x + SQUARE_SIZE - 5 && moveY > offsetItem.y + 5 && moveY < offsetItem.y + SQUARE_SIZE- 5) {
          const isAlreadySelected = gestureSelectionList.find(
            item => item.id === offsetItem.id,
          );
          if (!isAlreadySelected) {
            setGestureSelectionList(prevState => [...prevState, offsetItem.id]);
          }
        } 
      });
    }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [translate]);

这是我的测试用例和 here is a snack of the current implementation

对角线选择

水平选择

垂直选择

混选