APSL / react-native-keyboard-aware-scroll-view

A ScrollView component that handles keyboard appearance and automatically scrolls to focused TextInput.
MIT License
5.24k stars 643 forks source link

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

Open ysmike opened 3 years ago

ysmike commented 3 years ago

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!

EduVencovsky commented 3 years ago

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

charlestbell commented 1 year ago

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

charlestbell commented 1 year ago

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);
Humad commented 1 year ago

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

charlestbell commented 1 year ago

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

vsofroniev commented 1 year ago

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

loromagnoni commented 11 months ago

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 :)

charlestbell commented 11 months ago

@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.