computerjazz / react-native-draggable-flatlist

A drag-and-drop-enabled FlatList for React Native
MIT License
1.95k stars 407 forks source link

Not able to scroll if using DraggleFlatlist inside DraggleFlatlist . #292

Open pankaj210891 opened 3 years ago

pankaj210891 commented 3 years ago
        <DraggableFlatList
          nestedScrollEnabled={false}
          data={listDataSource}
          extraData={listDataSource}
          onDragBegin={() => isActive.setValue(1)}
          onRelease={() => isActive.setValue(0)}
          keyExtractor={(item, index) => `draggable-item-${item.id}`}
          // keyExtractor={(item, index) => item.id.toString()}
          renderItem={renderItem}
          onDragEnd={({data}) => setListDataSource(data)}
        />

const renderItem = ({item, index, drag, isActive}) => { return ( <ExpandableComponent drag={drag} isActive={isActive} scale={isActive ? animState.position : 1} dataSource={listDataSource} key={index} isEdit={getEditAndSort} parentIndex={index} setCreateVisibility={setCreateVisibility} setEditableMenuData={setEditableMenuData} setCategoryActiveStatus={(status, index) => { setCategoryActiveStatus(toInt(status), index); }} setMenuItemActiveStatus={(status, parentIndex, index) => { setMenuItemActiveStatus(toInt(status), parentIndex, index); }} onClickFunction={() => { updateLayout(index); }} setChildDataToMainList={(listData, parentIndex) => { //console.log(setChildDataToMainList ${parentIndex});

      let tempArray = [...listDataSource];

      tempArray[parentIndex].menu_items = listData;

      setListDataSource(tempArray);
    }}
    item={item}
  />
);

};

const ExpandableComponent = ({ dataSource, item, onClickFunction, isEdit, drag, scale, isActive, parentIndex, setChildDataToMainList, setCategoryActiveStatus, setMenuItemActiveStatus, setEditableMenuData, setCreateVisibility, }) => { //Custom Component for the Expandable List const [layoutHeight, setLayoutHeight] = useState(0);

const {t, i18n} = useTranslation();

useEffect(() => { if (item.is_expandable) { setLayoutHeight(null); } else { setLayoutHeight(0); } }, [item.is_expandable]);

// console.log(dataSource scale = ${key});

var isActiveChild = new Animated.Value(0); var clock = new Clock(); var animConfig = { damping: 20, mass: 0.4, stiffness: 100, overshootClamping: false, restSpeedThreshold: 0.2, restDisplacementThreshold: 0.2, toValue: new Value(0), }; var animState = { finished: new Value(0), velocity: new Value(0), position: new Value(1), time: new Value(0), };

const renderItemChild = ({item, index, drag, isActive}) => { var scale = isActive ? animState.position : 1; // var scale = isActive ? new Value(0.9) : 1;

//  console.log(`renderItemChild scale ${scale}`);

return (
  <Animated.View
    style={{
      justifyContent: 'center',
      alignItems: 'center',
      width: '100%',
      elevation: isActive ? 10 : 0,
      shadowRadius: isActive ? 10 : 0,
      shadowColor: isActive ? 'black' : 'transparent',
      shadowOpacity: isActive ? 0.25 : 0,
      transform: [
        {scaleY: isActive ? (Platform.OS === 'web' ? 1.5 : 1.2) : 1},
      ],
    }}>
    <TouchableOpacity
      onPress={() => {
        if (!isEdit) {
          var menuCategoriesIds = item.menu_category_ids;
          let filteredData = dataSource.filter((categoryObj) => {
            return menuCategoriesIds.find(
              (categoryId) => categoryId === categoryObj.id,
            );
          });

          item.selectedCategories = JSON.parse(
            JSON.stringify(filteredData),
          );

          // setEditableData(item);
          // setEdit(true);
          var body = {
            isEdit: true,
            editableData: item,
          };
          setEditableMenuData(body);
          // EventBus.publish(Constants.events.OPEN_CREATE_MODAL, true);
          setCreateVisibility(true);

          // debouncedSendEditData(body);
        }
      }}
      style={[
        styles.content,
        {
          alignSelf: 'center',
          // width: isActive ? '90%' : '100%',
          width: '100%',
          backgroundColor: !item.is_active
            ? Color.DEACTIVATED_COLOR
            : Color.DARK_GREY_LIST_HEADER,
        },
      ]}>
      <View
        style={{
          justifyContent: 'space-between',
          flexDirection: 'row',
          width:
            Platform.OS === 'web'
              ? isEdit
                ? '95%'
                : '100%'
              : isEdit
              ? '85%'
              : '100%',
          alignItems: 'stretch',
        }}>
        <View
          style={{
            flexDirection: 'row',
            paddingVertical: 16,
          }}>
          <Text
            style={[
              styles.skuText,
              {
                opacity: 1,
                color: !item.is_active
                  ? '#3D3D3D'
                  : Color.CUSTOMER_DETAILS_HEADER,
              },
            ]}
            numberOfLines={1}>
            {/* {item.sku ? `[${item.sku}]` : null} */}
            {item.sku
              ? `${
                  String(item.sku).length > 5
                    ? `[${String(item.sku).substr(0, 5)}...]`
                    : `[${item.sku}]`
                }`
              : null}
          </Text>
          <Text
            style={[
              styles.text,
              {
                opacity: 1,
                color: !item.is_active ? '#3D3D3D' : '#F2F2F2',
              },
            ]}>
            {item.name}
          </Text>
        </View>
        <View
          style={{
            flexDirection: 'row',
            justifyContent: 'center',
            alignItems: 'center',
          }}>
          <View
            style={{
              flexDirection: 'row',
              marginHorizontal: isEdit ? 15 : 20,
            }}>
            {!item.is_active ? (
              <Text style={styles.txtDeactivated}>
                {t('DeactivatedKey')}
              </Text>
            ) : null}

            <Text
              style={[
                styles.txtPrice,
                {
                  opacity: 0.9,
                  color: !item.is_active ? '#3D3D3D' : '#F2F2F2',
                },
              ]}>
              {/* {item.price} */}

              {item.price === null
                ? GetFormattedCurrency(
                    Number(item.menu_item_sizes[0].price),
                    GERMAN_COUN_CODE,
                    GERMAN_CURR,
                  )
                : GetFormattedCurrency(
                    Number(item.price),
                    GERMAN_COUN_CODE,
                    GERMAN_CURR,
                  )}

              {/* {item.price !== null ? ' €' : ''} */}
            </Text>
          </View>
        </View>
      </View>

      {isEdit ? (
        <View
          style={{
            width: Platform.OS === 'web' ? '5%' : isEdit ? '15%' : '0%',
            flexDirection: 'row',
            justifyContent: 'space-between',
            alignItems: 'stretch',
          }}>
          <View
            style={{
              justifyContent: 'center',
              alignItems: 'center',
            }}>
            <TouchableOpacity
              onPressIn={() => {
                // console.log('onLongPress');
                drag();
                if (Platform.OS == 'ios') {
                  PusherUtil.playVibration();
                } else {
                  Vibration.vibrate();
                }
              }}
              style={{
                width: 35,
                height: 35,
                alignSelf: 'center',
                justifyContent: 'center',
                alignItems: 'center',
              }}>
              <Icon
                name={'reorder-three-outline'}
                size={30}
                color={'#CAD0D5'}
              />
            </TouchableOpacity>
          </View>

          <TouchableOpacity
            onPress={() => {
              setMenuItemActiveStatus(!item.is_active, parentIndex, index);
            }}
            style={{
              width: 30,
              height: 30,
              backgroundColor: Color.WHITE,
              justifyContent: 'center',
              alignItems: 'center',
            }}>
            <Icon
              name={item.is_active ? 'remove-outline' : 'add-outline'}
              size={25}
              color={Color.BLACK}
            />
          </TouchableOpacity>
        </View>
      ) : null}
    </TouchableOpacity>
  </Animated.View>
);

};

return ( <Animated.View key={item.id} style={{ width: '100%', flexDirection: 'column', transform: [ {scaleY: isActive ? (Platform.OS === 'web' ? 1.5 : 1.2) : 1}, ], }}> {/Header of the Expandable List Item/} <TouchableOpacity onPress={onClickFunction} style={[ styles.content, { width: '100%', backgroundColor: item.color_code ? item.color_code : '#1C8D17', }, ]}> <Animated.Text style={[ styles.headerText, { fontSize: isActive ? 20 : 16, paddingVertical: 16, width: Platform.OS === 'web' ? isEdit ? '95%' : '100%' : isEdit ? '65%' : '75%', }, ]}> {item.name} </Animated.Text> <View style={{ flexDirection: 'row', justifyContent: 'center', paddingHorizontal: 10, alignItems: 'center', }}> <Icon name={ item.is_expandable ? 'chevron-up-outline' : 'chevron-down-outline' } size={30} style={{alignSelf: 'center', marginHorizontal: 10}} color={Color.WHITE} /> {/ /} {isEdit ? ( <View style={{ width: Platform.OS === 'web' ? '5%' : isEdit ? '15%' : '0%', flexDirection: 'row', justifyContent: 'space-between', alignItems: 'stretch', }}> <View style={{ justifyContent: 'center', alignItems: 'center', }}> <TouchableOpacity onPressIn={() => { drag(); if (Platform.OS == 'ios') { PusherUtil.playVibration(); } else { Vibration.vibrate(); } }} style={{ width: 35, height: 35, alignSelf: 'center', justifyContent: 'center', alignItems: 'center', }}> <Icon name={'reorder-three-outline'} size={30} color={'#CAD0D5'} />

        <TouchableOpacity
          onPress={() => {
            setCategoryActiveStatus(!item.is_active, parentIndex);
          }}
          style={{
            width: 30,
            height: 30,
            backgroundColor: Color.WHITE,
            justifyContent: 'center',
            alignItems: 'center',
          }}>
          <Icon
            name={item.is_active ? 'remove-outline' : 'add-outline'}
            size={25}
            color={Color.BLACK}
          />
        </TouchableOpacity>
      </View>
    ) : null}
  </TouchableOpacity>
  <View
    style={{
      height: layoutHeight,
      overflow: 'hidden',
      marginBottom: 10,
    }}>
    {/*Content under the header of the Expandable List Item*/}

    <DraggableFlatList
      nestedScrollEnabled={true}
      data={item.menu_items}
      extraData={item.menu_items}
      keyExtractor={(item, index) => item.id.toString()}
      renderItem={renderItemChild}
      onDragEnd={({data}) => setChildDataToMainList(data, parentIndex)}
    />
  </View>
</Animated.View>

); };

As you can see I have a DraggableFlatList which has list of data with structure like categories and categories has list of items. List of item are also in DraggableFlatList.

When I click on category name list expand. We can drag and drop both by category and items inside a category.

Now the issue is when I expand a category I can drag and drop the item but not able to scroll the list. For more understanding a video of problem is attached.

Also, I am passing 19 item but DraggableFlatList show only 10 some times and some time all.

https://user-images.githubusercontent.com/22121472/119064119-41afcb80-b9f8-11eb-83ed-25ba521183aa.mp4

Any help appreciated.

computerjazz commented 3 years ago

hm, you probably need to add simultaneousHandlers. I'm not sure if you can do this with what is currently exposed.

pankaj210891 commented 3 years ago

hm, you probably need to add simultaneousHandlers. I'm not sure if you can do this with what is currently exposed.

I tried to add simultaneousHandlers, but it didn't worked or may be I didn't used it properly.

If possible can you share a example with DraggleFlatList inside DraggleFlatList with simultaneousHandlers.

pankaj210891 commented 3 years ago

@computerjazz Thanks for the great lib. I solved my issue with your simultaneousHandlers and activationDistance. Once again thanks.

pankaj210891 commented 3 years ago

With activationDistance i am able to scroll but now drag and drop is not working for inside draggableflatlist. @computerjazz Please help.

matanrokach-honeybook commented 3 years ago

@pankaj210891 could you please add an example to demonstrate how to solve this issue? I am stuck with the same thing...

Skr1pt1k commented 3 years ago

@matanrokach-honeybook I've fixed a problem with scrolling like this:

Add a activationDistance const to check state. const [activationDistance, setActivationDistance] = useState(100);

Add a scrollEnabled prop for parent scrollview. <ScrollView style={{flex: 1}} scrollEnabled={activationDistance !== 0}>

Set activeDistance prop

<DraggableFlatList
   ...
   activationDistance={activationDistance}
   ...
</DraggableFlatList>

setActivationDistance hook I've used on onDragEnd function and long-press element

For me, it is working fine.

pankaj210891 commented 3 years ago

@pankaj210891 could you please add an example to demonstrate how to solve this issue? I am stuck with the same thing...

I managed to solve this issue...with dragHitSlop={{vertical:isDragBegin ? 0 :-100}} OnDragBegin={()=>{ SetDragBegin(true)}}

OnDragEnd={()=>{ SetDragEnd(false)}}

If you need anymore help, please let me know.

Donhv commented 3 years ago

i have the same issue with Horizontal DraggleFlatlist inside DraggleFlatlist

pxmage commented 2 years ago

@matanrokach-honeybook I've fixed a problem with scrolling like this:

Add a activationDistance const to check state. const [activationDistance, setActivationDistance] = useState(100);

Add a scrollEnabled prop for parent scrollview. <ScrollView style={{flex: 1}} scrollEnabled={activationDistance !== 0}>

Set activeDistance prop

<DraggableFlatList
   ...
   activationDistance={activationDistance}
   ...
</DraggableFlatList>

setActivationDistance hook I've used on onDragEnd function and long-press element

For me, it is working fine.

worked for me, thanks!

xclidongbo commented 2 years ago

@Skr1pt1k @pxmage Demo Please. I have the same problem.

IsmetGlumcevic commented 2 years ago

` const renderItem = ({ item, drag, isActive }) => { return (

{item.name} setData2(data)} keyExtractor={item => item.id} renderItem={renderItem2} />
       )
     };

     <DraggableFlatList
            data={data.filter(item => item.closed === false)}
            extraData={data}
            horizontal
            onDragEnd={({ data, from, to }) => setDtata(data)}
            keyExtractor={item => item.id}
            renderItem={renderItem}
        />

`

Draggable items inside NestableDraggableFlatList don't work.

Strengthless commented 1 year ago

Bump, any solutions?

The one mentioned by @Skr1pt1k was the closest (achieving both draggability and scrollability), but it still doesn't fully solve the problem for me... While the list now scrolls properly, it doesn't allow dragging of items on the first long press, only after releasing the finger and reclicking on the item now drags it properly. (Sorry for the poor description, it's a super weird behaviour, let me know if anyone wants a video demonstration)

Edit: We ended up redesigning the feature to use two separate lists instead of having them nested inside one another.

trungledangAxonActive commented 1 year ago

@Strengthless I'm facing the same issue when using NestableDraggableFlatList inside NestableDraggableFlatList

trungledangAxonActive commented 1 year ago

I have to use activationDistance for the children NestableDraggableFlatList to make the Parent NestableDraggableFlatList draggable but doing this will make the item in children NestableDraggableFlatList not draggable anymore

nurabunamus commented 4 months ago

Dragging and dropping functionality works well. But, scrolling does not work. I have tried previous solutions but they did not work. Can anyone help?

Screenshot 2024-07-05 at 14 22 00

keyExtractor = (item: {id: {toString: () => any}}) => item.id.toString();

pan = Gesture.Pan(); tap = Gesture.Tap();

composed = Gesture.Simultaneous(this.pan, this.tap);

renderItem = ({ item, drag, }: { item: {id: string; customName: string; targetLink: string; imgKey: string}; drag: () => void; }) => { const {id, customName, targetLink, imgKey} = item; console.log('tett dradding', this.state.isDragging);

return (
  <View style={styles.linkRow}>
    {this.state.isSelectionMode && (
      <CheckBoxWithLabel
        title=""
        status={
          this.state.selectedLinks.includes(id)
            ? CheckBoxStatus.checked
            : CheckBoxStatus.default
        }
        onClick={() => {
          const newStatus = this.state.selectedLinks.includes(id)
            ? CheckBoxStatus.default
            : CheckBoxStatus.checked;
          this.handleCheckboxChange(item.id, newStatus);
        }}
        style={styles.checkbox}
      />
    )}

    <View
      style={
        this.state.isSelectionMode
          ? styles.socialLinkCreatedRowSelection
          : styles.socialLinkCreatedRow
      }>
      <GestureDetector gesture={this.composed}>
        <SocialLinkCreatedRow
          type={this.state.campaignStateModel?.typeHash}
          id={id}
          editListItem={(id, link, name) => {
            this.setState({
              editListItem: {
                id: id,
                link: link,
                title: name,
              },
            });
          }}
          title={customName}
          image={imgKey}
          link={targetLink}
          onDragStart={() => {
            this.setState({isDragging: true});
            drag();
          }}
          onDragEnd={() => {
            this.setState({isDragging: false});
          }}
          isDragging={this.state.isDragging}
        />
      </GestureDetector>
    </View>
  </View>
);

};

onReordered = async ({ data, from, to, }: { data: { id: string; customName: string; targetLink: string; imgKey: string; }[];

from: number;
to: number;

}) => { const {filteredLinks} = this.state;

const movedItemId = filteredLinks[from]?.id;

this.setState({
  activationDistance: 0,
  filteredLinks: data,
  isLoading: true,
});

try {
  await this.changeOrderOfTargetLinks(movedItemId, to);
} catch (err) {
  console.error('Error updating order:', err);
} finally {
  // Restore activationDistance and setLoading state after drag ends
  this.setState({isLoading: false}, () => {
    // Delayed setState to ensure drag end is fully processed
    setTimeout(() => {
      this.setState({activationDistance: 100});
    }, 100);
  });
}

};