我正在尝试在 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。
对角线选择
水平选择
垂直选择
混选
我正在尝试通过 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。
对角线选择
水平选择
垂直选择
混选