Skip to content

Commit

Permalink
refactor: stable bottom padding for AwareScrollView (measure by targe…
Browse files Browse the repository at this point in the history
…t) (#193)

## 📜 Description

Stable bottom padding for `AwareScrollView`. Instead of touch
coordinates measure layout by tag.

## 💡 Motivation and Context

It's a simple showcase of a new functionality. Now we can consume
`target` and calculate layout for understanding whether we need to
scroll to focused TextInput or not.

This example is just for paper architecture. PR for Fabric support will
land later, since on Fabric we can not measure by tag (instead we need
to use ShadowTree nodes).

## 📢 Changelog

### JS
- measure layout instead of relying on touch;

## 🤔 How Has This Been Tested?

Tested on:
- iPhone 14 Pro (iOS 16.5, simulator);
- Pixel 7 Pro (Android 13, real device).

## 📸 Screenshots (if appropriate):

|Before|After|
|------|-----|
|<video
src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/9b187a54-88f0-4b39-9530-c0e2d0138341">|<video
src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/cb316406-fbcf-4632-af5e-46032e5f6f93">|
|<img width="473" alt="image"
src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/5934edc9-81f6-49a3-98d2-b48b08e69888">|<img
width="474" alt="image"
src="https://github.com/kirillzyusko/react-native-keyboard-controller/assets/22820318/85946f78-5ae0-497b-88f7-23449df5803b">|

## 📝 Checklist

- [x] CI successfully passed
  • Loading branch information
kirillzyusko committed Jul 20, 2023
1 parent c2a9776 commit 95a5376
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 20 deletions.
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
import React, { FC, useCallback } from 'react';
import {
GestureResponderEvent,
ScrollViewProps,
useWindowDimensions,
} from 'react-native';
import { useResizeMode } from 'react-native-keyboard-controller';
import React, { Component, FC } from 'react';
import { ScrollViewProps, useWindowDimensions } from 'react-native';
import Reanimated, {
MeasuredDimensions,
interpolate,
measure,
scrollTo,
useAnimatedRef,
useAnimatedScrollHandler,
useAnimatedStyle,
useSharedValue,
useWorkletCallback,
} from 'react-native-reanimated';
import { RefObjectFunction } from 'react-native-reanimated/lib/types/lib/reanimated2/hook/commonTypes';
import { useSmoothKeyboardHandler } from './useSmoothKeyboardHandler';

const BOTTOM_OFFSET = 50;
Expand All @@ -22,14 +20,13 @@ const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
children,
...rest
}) => {
useResizeMode();

const scrollViewAnimatedRef = useAnimatedRef<Reanimated.ScrollView>();
const scrollPosition = useSharedValue(0);
const click = useSharedValue(0);
const position = useSharedValue(0);
const layout = useSharedValue<MeasuredDimensions | null>(null);
const fakeViewHeight = useSharedValue(0);
const keyboardHeight = useSharedValue(0);
const tag = useSharedValue(-1);

const { height } = useWindowDimensions();

Expand All @@ -41,13 +38,10 @@ const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
},
[]
);

const onContentTouch = useCallback((e: GestureResponderEvent) => {
// to prevent clicks when keyboard is animating
if (keyboardHeight.value === 0) {
click.value = e.nativeEvent.pageY;
scrollPosition.value = position.value;
}
const measureByTag = useWorkletCallback((viewTag: number) => {
return measure(
(() => viewTag) as unknown as RefObjectFunction<Component<{}, {}, any>>
);
}, []);

/**
Expand All @@ -59,12 +53,13 @@ const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
fakeViewHeight.value = e;

const visibleRect = height - keyboardHeight.value;
const point = (layout.value?.pageY || 0) + (layout.value?.height || 0);

if (visibleRect - click.value <= BOTTOM_OFFSET) {
if (visibleRect - point <= BOTTOM_OFFSET) {
const interpolatedScrollTo = interpolate(
e,
[0, keyboardHeight.value],
[0, keyboardHeight.value - (height - click.value) + BOTTOM_OFFSET]
[0, keyboardHeight.value - (height - point) + BOTTOM_OFFSET]
);
const targetScrollY =
Math.max(interpolatedScrollTo, 0) + scrollPosition.value;
Expand All @@ -78,6 +73,27 @@ const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
onStart: (e) => {
'worklet';

// keyboard will appear
if (e.height > 0 && keyboardHeight.value === 0) {
// persist scroll value
scrollPosition.value = position.value;
}

// focus was changed
if (
tag.value !== e.target ||
(keyboardHeight.value !== e.height && e.height > 0)
) {
tag.value = e.target;

if (tag.value !== -1) {
// save position of focused text input when keyboard starts to move
layout.value = measureByTag(e.target);
console.log('UPDATED LAYOUT::', layout.value);
}
}

// keyboard will appear or change its size
if (e.height > 0) {
// just persist height - later will be used in interpolation
keyboardHeight.value = e.height;
Expand Down Expand Up @@ -109,7 +125,6 @@ const KeyboardAwareScrollView: FC<ScrollViewProps> = ({
ref={scrollViewAnimatedRef}
{...rest}
onScroll={onScroll}
onTouchStart={onContentTouch}
scrollEventThrottle={16}
>
{children}
Expand Down
1 change: 1 addition & 0 deletions example/src/screens/Examples/AwareScrollView/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export default function AwareScrollView() {
key={i}
placeholder={`${i}`}
placeholderTextColor="black"
keyboardType={i % 2 === 0 ? 'numeric' : 'default'}
style={{
width: '100%',
height: 50,
Expand Down

0 comments on commit 95a5376

Please sign in to comment.