Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
  • Loading branch information
tero-paananen committed Mar 7, 2022
2 parents a28fd10 + 9078f10 commit 7240cfd
Show file tree
Hide file tree
Showing 4 changed files with 90 additions and 77 deletions.
51 changes: 24 additions & 27 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,48 +19,45 @@ const INITIAL_DATA = Array.from({length: 30}, (_, i) => {

const App = () => {
const [data, setData] = useState<MyItem[]>(INITIAL_DATA);
const [selected, setSelected] = useState<Item | undefined>(undefined);

const renderItem = useCallback(
({item}: {item: MyItem}) => {
return <MyListItem item={item} />;
},
// eslint-disable-next-line react-hooks/exhaustive-deps
[selected],
);

// Item move done and new item array received
const handleMove = useCallback(
(fromIndex: number, toIndex: number, items: Item[]) => {
console.log('handleMove ', {fromIndex, toIndex});
setSelected(undefined);
setData(items);
},
[],
);

const MyListItem = React.memo(({item}: {item: MyItem}) => {
const handleSelected = useCallback(() => {
console.log('selected', {item});
item.id === selected?.id ? setSelected(undefined) : setSelected(item);
}, [item]);
const backgroundColor = item.id === selected?.id ? 'lightgray' : undefined;
return (
<TouchableOpacity
onPress={handleSelected}
style={[styles.itemContainer, {backgroundColor}]}
key={item.id}>
<Text style={[styles.item, {height: item.height}]}>{item.title}</Text>
</TouchableOpacity>
);
});
// Your custom FlatList item
const MyListItem = React.memo(
({item, drag}: {item: MyItem; drag?: (id: string) => void}) => {
// Long press fires 'drag' to start item dragging
const handleLongPress = useCallback(() => {
drag && drag(item.id);
}, [drag, item.id]);
return (
<TouchableOpacity
onLongPress={handleLongPress}
style={styles.itemContainer}
key={item.id}>
<Text style={[styles.item, {height: item.height}]}>{item.title}</Text>
</TouchableOpacity>
);
},
);
const renderItem = useCallback(
({item, drag}: {item: MyItem; drag?: (id: string) => void}) => {
return <MyListItem item={item} drag={drag} />;
},
[],
);

return (
<View style={styles.container}>
<View style={styles.header} />
<DraggableFlatList
style={styles.list}
data={data}
selected={selected}
renderItem={renderItem}
onHandleMove={handleMove}
/>
Expand Down
65 changes: 41 additions & 24 deletions DraggableFlatList.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,5 @@
import React, {useMemo, useState, useCallback, useRef, useEffect} from 'react';
import {
View,
StyleSheet,
PanResponder,
Animated,
FlatList,
Platform,
} from 'react-native';
import {View, StyleSheet, PanResponder, Animated, FlatList} from 'react-native';
import debounce from 'lodash/debounce';

import DraggableItem from './DraggableItem';
Expand All @@ -28,15 +21,20 @@ const CustomDraggableFlatList = ({
renderItem,
style,
onHandleMove,
selected,
}: {
data: Item[];
renderItem: (itemData: FlatListItem) => JSX.Element;
renderItem: ({
item,
drag,
}: {
item: Item;
drag?: (id: string) => void;
}) => JSX.Element;
style: any;
onHandleMove: (fromIndex: number, toIndex: number, data: Item[]) => void;
selected?: Item | undefined;
}) => {
const [below, setBelow] = useState<Item | undefined>(undefined);
const [selected, setSelected] = useState<Item | undefined>(undefined);
const [layout, setLayout] = useState<{layout: Layout | undefined}>({
layout: undefined,
});
Expand Down Expand Up @@ -70,21 +68,26 @@ const CustomDraggableFlatList = ({
// https://reactnative.dev/docs/panresponder
// https://eveningkid.medium.com/the-basics-of-react-native-gestures-23061b5e89cf
PanResponder.create({
// Does this view want to become responder on the start of a touch?
onStartShouldSetPanResponder: () => false,
onStartShouldSetPanResponderCapture: () => false,

// Should child views be prevented from becoming responder on first touch?
onStartShouldSetPanResponderCapture: (event: any) => {
const {pageX, pageY} = event.nativeEvent;
pan.setValue({x: pageX, y: pageY});

return false;
},

// Called for every touch move on the View when it is not the responder
// does this view want to "claim" touch responsiveness?
onMoveShouldSetPanResponder: () => {
if (Platform.OS === 'windows') {
if (panningRef.current && selectedRef.current) {
// start capturing panning
return true;
} else {
if (selectedRef.current) {
// wser has done item selection
// start capturing panning
return true;
} else {
// user can scroll FlatList
return false;
}
// user can scroll FlatList
return false;
}
},
// onMoveShouldSetPanResponderCapture: () => true, // iOS: FlatList is not scrollable if true
Expand Down Expand Up @@ -166,6 +169,8 @@ const CustomDraggableFlatList = ({

const endPanning = () => {
setBelow(undefined);
setSelected(undefined);
selectedRef.current = undefined;
pan.setValue({x: 0, y: 0});
panningRef.current = false;
startMoveYRef.current = -1;
Expand All @@ -176,6 +181,15 @@ const CustomDraggableFlatList = ({
callNextScrollToPoint.current.cancel();
};

const drag = (id: string) => {
const index = dataRef.current.findIndex(d => d.id === id);
const item = index !== -1 ? dataRef.current[index] : undefined;
if (item) {
panningRef.current = true;
setSelected(item);
}
};

const moveItem = (fromIndex: number, toIndex: number, items: Item[]) => {
const mutable = [...items];
const item = mutable.splice(fromIndex, 1)[0];
Expand All @@ -188,7 +202,7 @@ const CustomDraggableFlatList = ({
setBelow(item);
};

const posY = (moveY: number) => {
const posFromPanResponderY = (moveY: number) => {
return moveY - (layoutRef.current?.y || 0) + scrollOffsetY.current;
};

Expand All @@ -201,7 +215,7 @@ const CustomDraggableFlatList = ({
};

const itemIndexFromTouchPoint = (moveY: number, items: Item[]) => {
const y = posY(moveY);
const y = posFromPanResponderY(moveY);
const index = items.findIndex((d: Item) => {
const itemLayout = itemLayoutMapRef.current.get(d.id);
if (!itemLayout || !layoutRef.current) {
Expand Down Expand Up @@ -340,7 +354,10 @@ const CustomDraggableFlatList = ({

return (
<DraggableItem itemData={itemData} setRef={setRef} below={below?.id}>
{renderItem(itemData)}
{renderItem({
item: itemData.item,
drag: drag,
})}
</DraggableItem>
);
};
Expand Down
49 changes: 24 additions & 25 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,46 +22,45 @@ Not tested in production.
```
const App = () => {
const [data, setData] = useState<MyItem[]>(INITIAL_DATA);
const [selected, setSelected] = useState<Item | undefined>(undefined);
// Handle item data update on move
// Item move done and new item array received
const handleMove = useCallback(
(fromIndex: number, toIndex: number, items: Item[]) => {
console.log('handleMove ', {fromIndex, toIndex});
setSelected(undefined);
setData(items);
},
[],
);
// Make your custom item for FlatList
const MyListItem = React.memo(({item}: {item: MyItem}) => {
const handleSelected = useCallback(() => {
console.log('selected', {item});
item.id === selected?.id ? setSelected(undefined) : setSelected(item);
}, [item]);
const backgroundColor = item.id === selected?.id ? 'lightgray' : undefined;
return (
<TouchableOpacity
onPress={handleSelected}
style={[styles.itemContainer, {backgroundColor}]}
key={item.id}>
<Text style={[styles.item, {height: item.height}]}>{item.title}</Text>
</TouchableOpacity>
);
});
const renderItem = useCallback(({item}: {item: MyItem}) => {
return <MyListItem item={item} />;
}, [selected]);
// Your custom FlatList item
const MyListItem = React.memo(
({item, drag}: {item: MyItem; drag?: (id: string) => void}) => {
// Long press fires 'drag' to start item dragging
const handleLongPress = useCallback(() => {
drag && drag(item.id);
}, [drag, item.id]);
return (
<TouchableOpacity
onLongPress={handleLongPress}
style={styles.itemContainer}
key={item.id}>
<Text style={[styles.item, {height: item.height}]}>{item.title}</Text>
</TouchableOpacity>
);
},
);
const renderItem = useCallback(
({item, drag}: {item: MyItem; drag?: (id: string) => void}) => {
return <MyListItem item={item} drag={drag} />;
},
[],
);
return (
<View style={styles.container}>
<View style={styles.header} />
<DraggableFlatList
style={styles.list}
data={data}
selected={selected}
renderItem={renderItem}
onHandleMove={handleMove}
/>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "DraggableFlatList",
"version": "0.0.3",
"version": "0.0.4",
"private": true,
"author": {
"name": "Tero Paananen",
Expand Down

0 comments on commit 7240cfd

Please sign in to comment.