我想在网络上使用 React Native 进行拖放,但我找不到任何可用于使其在网络和移动设备上工作的库。我发现的大多数软件包仅适用于移动设备,例如:react-native-drax、rn-dnd-kanban。如果我可以使用一个包来创建可在网络和移动设备上运行的拖放功能,请告诉我。
我已经制作了一个具有拖放功能的应用程序,带有react-native-gesture-handler ~2.16.1和react-native-reanimated ~3.10.1。但我所做的有一个问题,它只能在网络上运行,并且性能很差,消耗大量 CPU。
这是我正在编写的代码。
import React, { useEffect, useState, useRef } from "react";
import { StyleSheet, Text, View, FlatList, Dimensions } from "react-native";
import Animated, {
runOnJS,
useAnimatedStyle,
useSharedValue,
withSpring,
withTiming,
} from "react-native-reanimated";
import {
Gesture,
GestureDetector,
GestureHandlerRootView,
ScrollView,
} from "react-native-gesture-handler";
import { generateNewId } from "@/utils/index";
interface Card {
id: string;
title: string;
}
interface Column {
id: string;
title: string;
cards: Card[];
}
interface Position {
x: number;
y: number;
width: number;
height: number;
}
interface DragContext {
type: "card" | "column";
columnId: string;
cardId?: string;
cardTitle?: string;
}
const initialColumns: Column[] = [
{
id: "b1c1",
title: "To Do",
cards: [
{ id: "b1c1c1", title: "Workspace - Board" },
{ id: "b1c1c2", title: "Workspace - Priority" },
],
},
{
id: "b1c2",
title: "In Progress",
cards: [{ id: "b1c2c1", title: "Workspace - Drag And Drop" }],
},
{ id: "b1c3", title: "Done", cards: [] },
{
id: "b1c4",
title: "Upcoming",
cards: [{ id: "b1c4c1", title: "Workspace - Backends" }],
},
{
id: "b1c5",
title: "Pending",
cards: [{ id: "b1c5c1", title: "Workspace - Assignee" }],
},
];
function Box({
children,
style,
id,
activeBox,
setActiveBox,
columnId,
title,
isColumn = false,
onDragStart,
onDragMove,
onDragEnd,
measureRef,
dragHandleRef,
isDragging,
}: {
children: React.ReactNode;
style: any;
id: string;
activeBox: string;
setActiveBox: (id: string) => void;
columnId?: string;
title?: string;
isColumn?: boolean;
onDragStart?: (context: DragContext) => void;
onDragMove?: (x: number, y: number) => void;
onDragEnd?: (x: number, y: number) => void;
measureRef?: (ref: View) => void;
dragHandleRef?: React.RefObject<View>;
isDragging?: boolean;
}) {
const pressed = useSharedValue(false);
const offset = useSharedValue({ x: 0, y: 0 });
const boxRef = useRef<View>(null);
const startPosition = useSharedValue({ x: 0, y: 0 });
useEffect(() => {
if (boxRef.current && measureRef) {
measureRef(boxRef.current);
}
}, [measureRef]);
const pan = Gesture.Pan()
.minDistance(1)
.onBegin((event) => {
// Only start drag if touching drag handle for columns
if (isColumn && dragHandleRef?.current) {
dragHandleRef.current.measure((x, y, width, height, pageX, pageY) => {
const isTouchInHandle =
event.absoluteX >= pageX &&
event.absoluteX <= pageX + width &&
event.absoluteY >= pageY &&
event.absoluteY <= pageY + height;
if (!isTouchInHandle) return;
});
}
pressed.value = true;
startPosition.value = { x: event.absoluteX, y: event.absoluteY };
runOnJS(setActiveBox)(id);
if (onDragStart) {
runOnJS(onDragStart)({
type: isColumn ? "column" : "card",
columnId: isColumn ? id : columnId || "",
cardId: isColumn ? undefined : id,
cardTitle: isColumn ? undefined : title,
});
}
})
.onChange((event) => {
offset.value = {
x: event.translationX,
y: event.translationY,
};
if (onDragMove) {
runOnJS(onDragMove)(event.absoluteX, event.absoluteY);
}
})
.onFinalize((event) => {
if (onDragEnd) {
runOnJS(onDragEnd)(event.absoluteX, event.absoluteY);
}
offset.value = withSpring({ x: 0, y: 0 });
pressed.value = false;
if (activeBox === id) runOnJS(setActiveBox)("");
});
const animatedStyles = useAnimatedStyle(() => ({
transform: [
{ translateX: offset.value.x },
{ translateY: offset.value.y },
{ scale: withTiming(pressed.value ? 1.1 : 1) },
],
backgroundColor: pressed.value ? "#FFE04B" : style.backgroundColor,
borderRadius: withTiming(pressed.value ? 25 : 10, { duration: 200 }),
zIndex: pressed.value ? 999 : 1,
opacity: isDragging ? 0.5 : 1,
}));
return (
<View>
<Animated.View ref={boxRef} style={[style, animatedStyles]}>
<GestureDetector gesture={pan}>
<View ref={dragHandleRef} style={styles.dragHandle}>
<Text style={styles.dragHandleText}>:::</Text>
</View>
</GestureDetector>
<View>{children}</View>
</Animated.View>
</View>
);
}
export default function App() {
const [activeBox, setActiveBox] = useState("");
const [columns, setColumns] = useState(initialColumns);
const [columnMeasurements, setColumnMeasurements] = useState<
Map<string, Position>
>(new Map());
const [cardMeasurements, setCardMeasurements] = useState<
Map<string, Position>
>(new Map());
const [dragContext, setDragContext] = useState<DragContext | null>(null);
const [hoveredColumn, setHoveredColumn] = useState<string | null>(null);
const [hoveredCard, setHoveredCard] = useState<string | null>(null);
const columnHandleRefs = useRef<Map<string, React.RefObject<View>>>(
new Map()
);
// Initialize refs for column handles
useEffect(() => {
columns.forEach((column) => {
if (!columnHandleRefs.current.has(column.id)) {
columnHandleRefs.current.set(column.id, React.createRef<View>());
}
});
}, [columns]);
const measureColumn = (columnId: string) => (ref: View) => {
ref.measure((x, y, width, height, pageX, pageY) => {
setColumnMeasurements((prev) =>
new Map(prev).set(columnId, {
x: pageX,
y: pageY,
width,
height,
})
);
});
};
const measureCard = (cardId: string) => (ref: View) => {
ref.measure((x, y, width, height, pageX, pageY) => {
setCardMeasurements((prev) =>
new Map(prev).set(cardId, {
x: pageX,
y: pageY,
width,
height,
})
);
});
};
const handleDragStart = (context: DragContext) => {
setDragContext(context);
console.log("Started dragging:", context);
};
const handleDragMove = (x: number, y: number) => {
if (!dragContext) return;
// Reset hoveredCard and hoveredColumn flags initially
let isHoveringCard = false;
let isHoveringColumn = false;
// Handle column movement
columnMeasurements.forEach((position, columnId) => {
if (
x >= position.x &&
x <= position.x + position.width &&
y >= position.y &&
y <= position.y + position.height
) {
isHoveringColumn = true; // Set flag if hovering over a column
if (hoveredColumn !== columnId) {
setHoveredColumn(columnId);
console.log(`Hovering over column: ${columnId}`);
}
}
});
// If not hovering over any column, reset hoveredColumn
if (!isHoveringColumn) {
setHoveredColumn(null);
}
// Handle card movement
cardMeasurements.forEach((position, cardId) => {
if (
cardId !== dragContext.cardId &&
x >= position.x &&
x <= position.x + position.width &&
y >= position.y &&
y <= position.y + position.height
) {
isHoveringCard = true; // Set flag if hovering over a card
if (hoveredCard !== cardId) {
setHoveredCard(cardId);
console.log(`Hovering over card: ${cardId}`);
}
}
});
// If not hovering over any card, reset hoveredCard
if (!isHoveringCard) {
setHoveredCard(null);
}
};
function handleDragEnd() {
if (!dragContext) return;
// Check if the drag ended over a column
if (dragContext.type === "card" && hoveredColumn) {
setColumns((prevColumns) =>
prevColumns.map((column) => {
if (column.id === hoveredColumn) {
let updatedCards = column.cards.filter(
(card) => card.id !== dragContext.cardId
);
const targetIndex = hoveredCard
? updatedCards.findIndex((card) => card.id === hoveredCard)
: updatedCards.length;
updatedCards.splice(targetIndex, 0, {
id: dragContext.cardId!,
title: dragContext.cardTitle!,
});
return { ...column, cards: updatedCards };
} else if (column.id === dragContext.columnId) {
// Remove card from its original column
return {
...column,
cards: column.cards.filter(
(card) => card.id !== dragContext.cardId
),
};
}
return column;
})
);
}
// // Reset states
setDragContext(null);
setHoveredColumn(null);
setHoveredCard(null);
return;
}
return (
<GestureHandlerRootView style={styles.container}>
<ScrollView horizontal style={styles.scrollView}>
{columns.map((column) => (
<Box
key={column.id}
id={column.id}
activeBox={activeBox}
setActiveBox={setActiveBox}
style={[
styles.columnBox,
hoveredColumn === column.id && styles.columnBoxHovered,
]}
isColumn={true}
onDragStart={handleDragStart}
onDragMove={handleDragMove}
onDragEnd={handleDragEnd}
measureRef={measureColumn(column.id)}
dragHandleRef={columnHandleRefs.current.get(column.id)}
>
<Text style={styles.columnTitle}>{column.title}</Text>
{column.cards.map((card) => (
<Box
key={card.id}
id={card.id}
title={card.title}
activeBox={activeBox}
setActiveBox={setActiveBox}
columnId={column.id}
style={[
styles.card,
hoveredCard === card.id && styles.cardHovered,
]}
onDragStart={handleDragStart}
onDragMove={handleDragMove}
onDragEnd={handleDragEnd}
measureRef={measureCard(card.id)}
isDragging={dragContext?.cardId === card.id}
>
<Text style={styles.cardTitle}>{card.title}</Text>
</Box>
))}
</Box>
))}
</ScrollView>
</GestureHandlerRootView>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
scrollView: {
flex: 1,
},
columnBox: {
padding: 10,
backgroundColor: "#e0e0e0e0",
borderRadius: 8,
width: 280,
margin: 10,
minHeight: 400,
},
columnBoxHovered: {
backgroundColor: "#e0e0e0",
borderColor: "#2196F3",
borderWidth: 2,
},
columnTitle: {
fontSize: 18,
fontWeight: "600",
marginBottom: 10,
color: "#333",
},
card: {
marginVertical: 5,
padding: 15,
backgroundColor: "white",
borderRadius: 8,
width: "100%",
height: 100,
shadowColor: "#000",
shadowOffset: {
width: 0,
height: 2,
},
shadowOpacity: 0.1,
shadowRadius: 3,
elevation: 3,
},
cardHovered: {
backgroundColor: "#f8f8f8",
borderColor: "#2196F3",
borderWidth: 2,
},
cardTitle: {
fontSize: 16,
color: "#333",
},
dragHandle: {
padding: 5,
alignItems: "center",
justifyContent: "center",
},
dragHandleText: {
fontSize: 16,
color: "#999",
},
});
你看过https://github.com/react-grid-layout/react-grid-layout吗?
它支持拖放,还支持使用 React 在 Web 上调整大小,我相信有些人已经通过添加按住切换来启用拖动与滚动来使其在移动界面上运行良好。
我还有一个网络演示模板这里。
希望这有帮助!