Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ANDROID] - Jerks when an input is inside a scrollview with animations #6817

Open
LcsGrz opened this issue Dec 12, 2024 · 1 comment
Open
Labels
Platform: Android This issue is specific to Android Repro provided A reproduction with a snippet of code, snack or repo is provided

Comments

@LcsGrz
Copy link

LcsGrz commented Dec 12, 2024

Description

I found a problem that I can not find a solution, I can not understand if it is an issue of the library or something of the operating system but my conclusions tell me that the problem may come from here.

I'll start explaining, it's super simple, it only happens in android and if some conditions are given as for example with certain animations and if the animations are active.

in simple words when there is an input with focus inside a scroll view that above the scrollview there is a component with animations and you want to scroll, it starts to make jerks without being able to hide the input inside the scroll, unless you scroll with 'a lot of force'.

Steps to reproduce

Here I leave some videos to understand the issue, to understand the component is simple, I will leave an example of how this conformed

<View>
  <View> // has animations
    <Title/>  // has animations
    <Subtitle/>  // has animations
  </View>
  <ScrollView>
    <Input/> x 20
  </ScrollView>
</View>

in this first video you can see the animation that has the upper component and then the issue, when there is no input in focus the scroll is normal, no problem, but when there is an input with focus and the animation is not 100% done, for some reason I can not 'hide' the input by scrolling, from above or below.

Screen.Recording.2024-12-12.at.1.22.14.PM.mov

here I show again the issue

Screen.Recording.2024-12-12.at.1.22.44.PM.mov

and finally I show how the screen behaves when I select inputs that are below the scroll, when the animation reaches 100% of its range, the issue is no longer present.

Screen.Recording.2024-12-12.at.1.23.13.PM.mov

I will share the 3 files needed to understand this problem



Screen

import { View, TextInput } from 'react-native';
import { AnimatedHeaderText } from '@design-system/components';
import Animated, {
  useAnimatedScrollHandler,
  useSharedValue,
} from 'react-native-reanimated';

export default function VetstoriaClinicDetails() {
  const verticalScrollValue = useSharedValue(0);

  const scrollHandler = useAnimatedScrollHandler({
    onScroll: event => {
      verticalScrollValue.value = event.contentOffset.y;
    },
  });

  const style = {
    marginTop: 30,
    backgroundColor: 'pink',
    marginHorizontal: 26,
  };

  return (
    <View style={{ flex: 1, marginTop: 26 }}>
      <AnimatedHeaderText
        title="TITLE TEST TITLE TEST TITLE TEST TITLE TEST"
        subtitle="SUBTITLE TEST SUBTITLE TEST SUBTITLE TEST SUBTITLE TEST"
        scrollValue={verticalScrollValue}
      />
      <Animated.ScrollView
        onScroll={scrollHandler}
        scrollEventThrottle={16}>
        <TextInput placeholder="test" style={style} />
        <TextInput placeholder="test" style={style} />
        <TextInput placeholder="test" style={style} />
        <TextInput placeholder="test" style={style} />
        <TextInput placeholder="test" style={style} />
        <TextInput placeholder="test" style={style} />
        <TextInput placeholder="test" style={style} />
        <TextInput placeholder="test" style={style} />
        <TextInput placeholder="test" style={style} />
        <TextInput placeholder="test" style={style} />
        <TextInput placeholder="test" style={style} />
        <TextInput placeholder="test" style={style} />
        <TextInput placeholder="test" style={style} />
        <TextInput placeholder="test" style={style} />
        <TextInput placeholder="test" style={style} />
        <TextInput placeholder="test" style={style} />
        <TextInput placeholder="test" style={style} />
      </Animated.ScrollView>
    </View>
  );
}

AnimatedHeaderText

import { useState } from 'react';
import { LayoutChangeEvent, StyleSheet, View } from 'react-native';
import Animated, { useSharedValue } from 'react-native-reanimated';

import { useCreateScrollAnimations } from './helpers';

export default function AnimatedHeaderText({ title, subtitle, scrollValue }) {
  const [subtitleHeight, setSubtitleHeight] = useState(0);

  const { animatedContainerStyle, animatedTitleStyle, animatedSubtitleStyle } =
    useCreateScrollAnimations(scrollValue, subtitleHeight);

  const handleSubtitleLayout = (event: LayoutChangeEvent) => {
    const { height = 0 } = event.nativeEvent.layout;

    setSubtitleHeight(height);
  };

  return (
    <Animated.View style={[styles.container, animatedContainerStyle]}>
      <Animated.Text style={[styles.title, animatedTitleStyle]}>
        {title}
      </Animated.Text>
      <View style={styles.subtitleBox}>
        <Animated.Text
          onLayout={handleSubtitleLayout}
          style={[styles.subtitle, animatedSubtitleStyle]}>
          {subtitle}
        </Animated.Text>
      </View>
    </Animated.View>
  );
}

const styles = StyleSheet.create({
  container: {
    paddingHorizontal: 24,
  },
  title: {
    fontSize: 40,
    lineHeight: 40,
    letterSpacing: -1.2,
    paddingTop: 16,
    zIndex: 1,
  },
  subtitleBox: {
    overflow: 'hidden',
  },
  subtitle: {
    fontSize: 16,
    lineHeight: 20,
    zIndex: 2,
  },
});

helpers (useCreateScrollAnimations)

import { useMemo } from 'react';
import {
  interpolate,
  useAnimatedStyle,
  withSpring,
} from 'react-native-reanimated';

export const useCreateAnimatedStyles = (sharedValue, inputRange, layout) => {
  return useAnimatedStyle(() => {
    const animatedStyles = {};

    for (const [property, outputRange] of Object.entries(layout)) {
      const interpolatedValue = interpolate(
        sharedValue.value,
        inputRange,
        outputRange,
        'clamp',
      );

      animatedStyles[property] = withSpring(interpolatedValue, {
        damping: 20, // Increases resistance to prevent bounce back
        stiffness: 150, // Slightly less stiff for more fluidity
        mass: 0.8, // Reduces inertia
        overshootClamping: true, // Prevents overshooting the target value
      });
    }

    return animatedStyles;
  });
};

export const useCreateScrollAnimations = (scrollY, subtitleHeight) => {
  const layout = useMemo(() => {
    return {
      title: {
        fontSize: [40, 24],
        lineHeight: [40, 24],
        letterSpacing: [-1.2, -0.48],
      },
      subtitle: {
        opacity: [1, 0],
        marginTop: [24, -subtitleHeight],
      },
      container: {
        marginBottom: [40, 24],
      },
    };
  }, [subtitleHeight]);

  const animatedTitleStyle = useCreateAnimatedStyles(
    scrollY,
    [0, 200],
    layout.title,
  );

  const animatedContainerStyle = useCreateAnimatedStyles(
    scrollY,
    [0, 200],
    layout.container,
  );

  const animatedSubtitleStyle = useCreateAnimatedStyles(
    scrollY,
    [0, 200],
    layout.subtitle,
  );

  return {
    animatedTitleStyle,
    animatedSubtitleStyle,
    animatedContainerStyle,
  };
};


now that we understand this, what could be happening, first it makes you think that it is a behavior of the operating system because it 'prevents' the input with focus to be hidden but then when the animation is disabled or reached 100% the scroll works normally

only happens with certain animations, disabling some of them also returns to work correctly, but it breaks the experience I'm looking for.

Snack or a link to a repository

https://snack.expo.dev/_2zsCHdC2NwFnjlMrEg4B

Reanimated version

3.16.5

React Native version

0.73.6

Platforms

Android

JavaScript runtime

None

Workflow

React Native

Architecture

Fabric (New Architecture)

Build type

Debug app & production bundle

Device

Real device

Device model

No response

Acknowledgements

Yes

@github-actions github-actions bot added Platform: Android This issue is specific to Android Repro provided A reproduction with a snippet of code, snack or repo is provided labels Dec 12, 2024
@DarkShtir
Copy link

I Have similar issue, but with Touchable, when i have animation in header, all touchable not triggered

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Platform: Android This issue is specific to Android Repro provided A reproduction with a snippet of code, snack or repo is provided
Projects
None yet
Development

No branches or pull requests

2 participants