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

@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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions