react-native-keyboard-aware-scroll-view icon indicating copy to clipboard operation
react-native-keyboard-aware-scroll-view copied to clipboard

How to get the best of two worlds: keyboardaware & draggable flatlists

Open ysmike opened this issue 3 years ago • 8 comments

Is it possible to get the draggable functionality from draggableflatlist and the awesome keyboard aware functionality of this repo? Please let me know if there is a way to do this without running into scrolling errors!

ysmike avatar Apr 22 '21 14:04 ysmike

I think it's possible, but you would need to use KeyboardAwareHOC and wrap around the draggable flatlist.

As described here.

I'm not sure if it's compatible, so you might have extra steps

EduVencovsky avatar Apr 28 '21 14:04 EduVencovsky

@EduVencovsky You sir are a gentleman and a scholar! Been searching for that solution for ages.

charlestbell avatar Sep 25 '22 20:09 charlestbell

If anyone wants to do the same thing, here is my Keyboard Aware Draggable Flatlist

// In keyboard-aware-draggable-flatlist.js

import DraggableFlatList from 'react-native-draggable-flatlist';

import listenToKeyboardEvents from 'react-native-keyboard-aware-scroll-view/lib/KeyboardAwareHOC';

const config = {
  enableOnAndroid: true,
  enableAutomaticScroll: true,
};

export default listenToKeyboardEvents(config)(DraggableFlatList);

charlestbell avatar Sep 26 '22 00:09 charlestbell

Did this work well for you? Wrapping my flatlist with this HOC doesn't auto-scroll when list items move out of view

Humad avatar Feb 01 '23 01:02 Humad

It is working well. Fixing the scroll issue was a bit of a pain tho.

It's been awhile since I worked on it so I don't remember everything, but hopefully you can reverse-engineer my code.

 <KeyboardAwareDraggableFlatList
          data={fields}
          onDragEnd={({ from, to }) => {
            move(from, to);
          }}
          keyExtractor={item => item.id}
          renderItem={renderItem}
          ListFooterComponent={() => (
            <AddNewComponent append={append} scrollToIndex={scrollToIndex} />
          )}
          // eslint-disable-next-line react-native/no-inline-styles
          containerStyle={{ marginBottom: errors.name ? 188 : 161, flex: 1 }}
          // eslint-disable-next-line react-native/no-inline-styles
          contentContainerStyle={{ marginTop: 5 }}
          activationDistance={10}
          ref={scrollRef}
          getItemLayout={getItemLayout.bind(this)}
          enableResetScrollToCoords={false}
        />

export const AddNewComponent = ({ append, scrollToIndex }) => {
  return (
    <TouchableOpacity
      style={styles.addComponent}
      onPress={() => {
        append(new Component());
        scrollToIndex();
      }}
    >
      <FontAwesomeIcon icon={faPlus} color={Colors.neutral[4]} size={24} />
      <Text style={[styles.readOnly, styles.input]}>Add Component</Text>
    </TouchableOpacity>
  );
}
  const scrollToIndex = () => {
    setTimeout(
      () => {
        if (scrollRef && scrollRef.current) {
          scrollRef.current?.scrollToEnd();
        }
      },

      100
    );
  }
  const scrollRef = useRef(null)
  const renderItem = useCallback(params => {
    return (
      <ComponentListItem
        control={control}
        {...params}
        itemRefs={itemRefs}
        remove={remove}
        handleAllChecked={handleAllChecked}
      />
    );
  }, [])
const ComponentListItem = ({
  item,
  index,
  drag,
  isActive,
  control,
  itemRefs,
  remove,
  handleAllChecked,
}) => {
  const [snapPointsLeft] = useState([50]);
  // Closes other list items after 1second, if you open a new list item. Boilerplate from https://github.com/computerjazz/react-native-draggable-flatlist
  useEffect(() => {
    if (item.id === 'key-0') {
      setTimeout(() => {
        itemRefs.current
          ?.get(item.id)
          ?.open(OpenDirection.LEFT, undefined, { animated: true });
      }, 1000);
    }
  }, [item.id]);

  return (
    <ScaleDecorator>
      <SwipeableItem
        key={item.id}
        // item={item}
        ref={ref => {
          if (ref && !itemRefs.current.get(item.id)) {
            itemRefs.current.set(item.id, ref);
          }
        }}
        onChange={({ openDirection }) => {
          if (openDirection !== OpenDirection.NONE) {
            // Close all other open items
            [...itemRefs.current.entries()].forEach(([id, ref]) => {
              if (id !== item.id && ref) ref.close();
            });
          }
        }}
        overSwipe={OVERSWIPE_DIST}
        renderUnderlayLeft={() => (
          <UnderlayLeft drag={drag} remove={remove} index={index} />
        )}
        snapPointsLeft={snapPointsLeft}
      >
        <View style={styles.componentContainer}>
          <TouchableOpacity
            onLongPress={drag}
            disabled={isActive}
            style={[
              // eslint-disable-next-line react-native/no-inline-styles
              { backgroundColor: isActive ? Colors.primary[4] : null },
              styles.componentContainer,
            ]}
          >
            <FontAwesomeIcon
              style={styles.gripIcon}
              icon={faGripVertical}
              color={Colors.neutral[4]}
              size={24}
            />
            <View style={[styles.inputBox, styles.shadow]}>
              <Input
                name={`components[${index}].name`}
                control={control}
                style={{
                  ...styles.input,
                }}
                defaultValue={item.title}
                maxLength={40}
              />
            </View>
          </TouchableOpacity>
          <View style={styles.checkboxContainer}>
            <Checkbox
              name={`components[${index}].isChecked`}
              control={control}
              callback={handleAllChecked}
            />
          </View>
        </View>
      </SwipeableItem>
    </ScaleDecorator>
  );
};

Hopefully that was helpful

charlestbell avatar Feb 02 '23 14:02 charlestbell

@charlestbell - Do you happen to still have the getItemLayout function? Auto-scroll doesn't work when dragging using the above code.

vsofroniev avatar Jun 11 '23 12:06 vsofroniev

I also encountered the autoscroll missing when moving dragged items at the list edges. Ended up not using the library, for my use case it has been enough this single hook to make the list scroll when keyboard hovers content:

  useEffect(() => {
    const listener = Keyboard.addListener('keyboardDidShow', (e) => {
      const keyboardHeight = e.endCoordinates.height
      TextInput.State.currentlyFocusedInput().measure(
        (originX, originY, width, height, pageX, pageY) => {
          const yFromTop = pageY
          const componentHeight = height
          const screenHeight = Dimensions.get('window').height
          const yFromBottom = screenHeight - yFromTop - componentHeight
          const hiddenOffset = keyboardHeight - yFromBottom
          const margin = 32
          if (hiddenOffset > 0) {
            listRef.current?.scrollToOffset({
              animated: true,
              offset: scrollRef.current.value + hiddenOffset + margin,
            })
          }
        }
      )
    })
    return () => listener.remove()
  }, [])

Where scrollRef is just a ref value to keep track of the scrolling offset, and listRef reference the DraggableFlatList item.


const listRef = useRef<FlatList<YourType>>(null)
const scrollRef = useRef({ value: 0 })
...

<DraggableFlatList
       ref={listRef}
       onScrollOffsetChange={(e) => {
           scrollRef.current.value = e
       }}
...

Hope this can help someone :)

loromagnoni avatar Jul 20 '23 20:07 loromagnoni

@vsofroniev I ended up removing that function. I think it was causing issues on Android or something like that. I believe you can make auto scroll work without it, it's just slower.

charlestbell avatar Jul 24 '23 15:07 charlestbell