Skip to content

When I use this library it doesn't work on Android. Then I found this component and shared it with people who encountered the same problem as me. #583

Open
@turntoarctic

Description

@turntoarctic
import React, {useCallback, useEffect, useMemo, useRef, useState} from 'react';
import {View, Platform} from 'react-native';
import {Keyboard, ScrollView, TextInput, StatusBar} from 'react-native';

interface Props extends React.ComponentProps<typeof ScrollView> {
  additionalScrollHeight?: number;
}

const KeyboardScrollView = ({
  children,
  additionalScrollHeight,
  contentContainerStyle,
  ...props
}: Props) => {
  const scrollViewRef = useRef<ScrollView>(null);
  const scrollPositionRef = useRef<number>(0);
  const scrollContentSizeRef = useRef<number>(0);
  const scrollViewSizeRef = useRef<number>(0);

  const [isKeyboardVisible, setIsKeyboardVisible] = useState(false);
  const [additionalPadding, setAdditionalPadding] = useState(0);

  const scrollToPosition = useCallback(
    (toPosition: number, animated?: boolean) => {
      scrollViewRef.current?.scrollTo({y: toPosition, animated: !!animated});
      scrollPositionRef.current = toPosition;
    },
    [],
  );

  const additionalScroll = useMemo(
    () => additionalScrollHeight ?? 0,
    [additionalScrollHeight],
  );
  const androidStatusBarOffset = useMemo(
    () => StatusBar.currentHeight ?? 0,
    [],
  );

  useEffect(() => {
    const didShowListener = Keyboard.addListener('keyboardDidShow', frames => {
      const keyboardY = frames.endCoordinates.screenY;
      const keyboardHeight = frames.endCoordinates.height;
      setAdditionalPadding(Math.ceil(keyboardHeight));
  
      setTimeout(() => {
        setIsKeyboardVisible(true);
      }, 100);

      const currentlyFocusedInput = TextInput.State.currentlyFocusedInput();
      const currentScrollY = scrollPositionRef.current;

      currentlyFocusedInput?.measureInWindow((_x, y, _width, height) => {
        const endOfInputY = y + height + androidStatusBarOffset;
        const deltaToScroll = endOfInputY - keyboardY;

        if (deltaToScroll < 0) {
          return;
        }

        const scrollPositionTarget =
          currentScrollY + deltaToScroll + additionalScroll;
        scrollToPosition(scrollPositionTarget, true);
      });
    });

    const didHideListener = Keyboard.addListener('keyboardDidHide', () => {
      setAdditionalPadding(0);
      setIsKeyboardVisible(false);
    });

    const willHideListener = Keyboard.addListener(
      'keyboardWillHide',
      frames => {
        // iOS only, scroll back to initial position to avoid flickering
        const keyboardHeight = frames.endCoordinates.height;
        const currentScrollY = scrollPositionRef.current;

        if (currentScrollY <= 0) {
          return;
        }

        const scrollPositionTarget = currentScrollY - keyboardHeight;
        scrollToPosition(scrollPositionTarget, true);
      },
    );

    return () => {
      didShowListener.remove();
      didHideListener.remove();
      willHideListener.remove();
    };
  }, [additionalScroll, androidStatusBarOffset, scrollToPosition]);

  return (
    <ScrollView
      ref={scrollViewRef}
      contentContainerStyle={[contentContainerStyle]}
      contentInset={{bottom: additionalPadding}}
      keyboardShouldPersistTaps="never"
      onMomentumScrollEnd={event => {
        scrollPositionRef.current = event.nativeEvent.contentOffset.y;
      }}
      onScrollEndDrag={event => {
        scrollPositionRef.current = event.nativeEvent.contentOffset.y;
      }}
      onLayout={event => {
        scrollViewSizeRef.current = event.nativeEvent.layout.height;
      }}
      onContentSizeChange={(_width, height) => {
        const currentContentHeight = scrollContentSizeRef.current;
        const contentSizeDelta = height - currentContentHeight;
        scrollContentSizeRef.current = height;
        if (!isKeyboardVisible) {
          return;
        }
        const currentScrollY = scrollPositionRef.current;
        const scrollPositionTarget = currentScrollY + contentSizeDelta;
        scrollToPosition(scrollPositionTarget, true);
      }}
      {...props}>
      <View style={{paddingBottom: Platform.OS === 'ios' ? 0 : additionalPadding}}>
        {children}
      </View>
    </ScrollView>
  );
};

export default KeyboardScrollView;

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions