react-native: [Android][Animation] Interpolated translateY value causes android view to jump.

Environment

React Native Environment Info: System: OS: macOS 10.14 CPU: x64 Intel® Core™ i7-4770HQ CPU @ 2.20GHz Memory: 862.68 MB / 16.00 GB Shell: 3.2.57 - /bin/sh Binaries: Node: 10.9.0 - ~/.nvm/versions/node/v10.9.0/bin/node Yarn: 1.10.1 - ~/.nvm/versions/node/v10.9.0/bin/yarn npm: 6.2.0 - ~/.nvm/versions/node/v10.9.0/bin/npm Watchman: 4.9.0 - /usr/local/bin/watchman SDKs: iOS SDK: Platforms: iOS 11.2, macOS 10.13, tvOS 11.2, watchOS 4.2 IDEs: Android Studio: 3.1 AI-173.4819257 Xcode: 9.2/9C40b - /usr/bin/xcodebuild npmPackages: react: 16.6.0-alpha.8af6728 => 16.6.0-alpha.8af6728 react-native: ^0.57.3 => 0.57.3 npmGlobalPackages: react-native-cli: 2.0.1

Description

I’m trying to implement collapsible header using React Native Animated API. using event method i get AnimatedHeaderValue and interpolate it into translateY value. This value i apply to container of my listView (so listView moves vertically). IOS animation works perfectly, but Android animation jumping and lagging when i scroll. I tried to inscrease scroll value and Android animation became more smooth.

This is scrollView container that passes onScroll to scrollView (listView)

<ScCompanyNewsFeedList optimizeHeight getRef={scrollView => { console.log("SCROLL VIEW", scrollView) this._scrollView = scrollView; }} scrollEventThrottle = { 2 } onScroll={Animated.event( [{ nativeEvent: { contentOffset: { y: this.AnimatedHeaderValue }}}], )} companyId={this.props.companyId}/> }

this is base container that contains tabs and my scrollView. It’s moving when scroll.

<Animated.View style={[{flex: 1}, {transform: [{translateY: headerHeight}]}]}> ... </Animated.View>

Interpolation

const animationRange = this.AnimatedHeaderValue.interpolate({ inputRange: [0, scrollRange], outputRange: [0, 1], extrapolate: "clamp" }); const headerHeight2 = animationRange.interpolate({ inputRange: [0, 1], outputRange: [0, -200] });

About this issue

  • Original URL
  • State: open
  • Created 6 years ago
  • Reactions: 46
  • Comments: 123 (6 by maintainers)

Most upvoted comments

The issue still exists on rn 0.57

@kelset Yes. Here is a example tested on RN 58.1:

import React, { Component } from "react";
import { Animated, View, StyleSheet } from "react-native";

export default class Example extends Component {
  state = {
    scrollY: new Animated.Value(0)
  };

  render() {
    const marginTopAnimated = this.state.scrollY.interpolate({
      inputRange: [0, 100],
      outputRange: [100, 0],
      extrapolate: "clamp"
    });

    return (
      <View style={{ flex: 1, backgroundColor: "skyblue" }}>
        <Animated.ScrollView
          style={[
            styles.scrollView,
            {
              transform: [{ translateY: marginTopAnimated }]
            }
          ]}
          scrollEventThrottle={16}
          onScroll={Animated.event(
            [
              {
                nativeEvent: { contentOffset: { y: this.state.scrollY } }
              }
            ],
            { useNativeDriver: true }
          )}
        >
          <View style={{ height: 1000 }} />
        </Animated.ScrollView>
      </View>
    );
  }
}

const styles = StyleSheet.create({
  scrollView: {
    backgroundColor: "#FFF",
    borderTopRightRadius: 28,
    borderTopLeftRadius: 28
  }
});

Just create a new project with react-native init and paste it on App.js. Here is a gif of the result:

example-animation

bump again, can we get at least a milestone / time frame when this will be addressed?

+1

iOS ios

Android android

It looks like you are using an older version of React Native. Please update to the latest release, v0.57 and verify if the issue still exists.

The “⏪Old Version” label will be removed automatically once you edit your original post with the results of running react-native info on a project using the latest release.

any updates on this?

Dissapointed. The issue still open for more than two years…

I think this framework is dead.

I still stuck on android. Anyone have solutions for this ?

@benevbright Hello, I started using your react-navigation-collapsible unfortunately the bug on android where the header started to jump when scrolling slowly made it not suitable for production release. I made a hack way to avoid the “jumping bug” but i dont how to implement it on your library but i hope it helps

https://snack.expo.io/r1hwBv0wV - Here’s the one with “Jumping Bug” https://snack.expo.io/Hk34BPCDN - Here’s the one without the bug

I just add the following style

position: 'absolute',
zIndex: 999,
top: 0

i dont know but i think the position: 'absolute' removes the bug

The problem isn’t in android but lies somewhere in your code which deals with the height values. You can, smoothly transform, translateY number of Animated.Views simultanously e.g. Header view, middle view, bottom view using Pan, ScrollView or Flatlist. Note: You cannot animate using height property while using useNativeDriver: true, it should be false.

  1. Interpolation negative outputRange value should be less than equllTo inputRange value e.g. iR:[0, 200], oR:[0, -199]. Always greater than -201 and less than 200.
  2. Adjust paddingBottom in contentContainerStyle. You need to make it sure virtually that you have same invisible height as the sum of visible animating items area.
  3. useNativeDriver: true

Still happening in RN 0.63 😬

The issue still exists on rn 0.63

Hi there, The problem seems to be related to the animation range of the interpolate function. There seems to be some kind of “resonance” in the scroll animation for certain relative values. In my case (I am using it to make a header parallax effect), a simple workaround I found was to change the input range : From inputRange: [0,(HEADER_EXPANDED_HEIGHT-HEADER_HALF_HEIGHT)], to inputRange: [0,(HEADER_EXPANDED_HEIGHT-HEADER_HALF_HEIGHT)*1.5], I then put some bottom padding to the Animated.Scrollview component: contentContainerStyle={{paddingBottom: 25}} It is not very clean but it did the trick for me.

@nidorx Yes, I understood. But what I’m saying is that it’s a workaround. And here we want this bug will be fixed so we can use translateY on ScrollView.

I can confirm this issue still persists on 0.64.2. It’s a real bummer because we can’t implement our chosen designs on Android meanwhile they work perfectly well on iOS. 😢 Hoping this issue gets the attention it deserves, as animations are quickly becoming standard in react native dev’s toolkits. I’m still seeking a useful workaround, I will share if/when that comes around!

@react-native-bot @shergin @hramos @sahrens this issue is not fixed till now, Please if possible will you guys look into this issue. it’s almost 2 years gone but still, this issue is open.

Those who are looking for a solution to this issue, Just make your input range 2 times of that output range like this inputRange: [0, 2*HEADER_EXPANDED_HEIGHT], outputRange: [0, -HEADER_EXPANDED_HEIGHT]

@nidorx Hi, Thanks for the comment. Nice workaround.

But your solution is off the topic. Because you don’t use translateY on ScrollView but gave top margin and translate “Header”.

This bug is about using translateY on ScrollView.

0.66.3 same only android

iOS ios

Android android

iOS was never a problem, but it was android which was causing the issue, I also don’t have any other suggestion If it’s not working 😦

@kelset on 0.62.0-rc.0 still persists.

note: My hand is fine.

Jan-07-2020 22-02-50

+1

I was trying to implement collapsible header using reanimated and meet this dumb problem on Android. No matter what I use to animate ScrollView (paddingTop, translateY, height). This is quite serious bug and it exists for a really long time, I don’t believe that using react-native I cannot implement such an easy animation on Android…

It has redproduced since 2017: https://github.com/facebook/react-native/issues/15445

If there aren’t any blocks, contributors please pay attention to this problem

Has anyone tested this on RN 0.64?

@benevbright, I’m implementing an application to document a component library I own.

In the app, the header is animated and I went through the same problem described here.

To solve the problem, I made the following adjustments to my component.

1 . Additional content is passed through the page to NavigationHeader via props 2. NavigationHeader looks at content height and tells page about new size 3. The page, which has ScrollView (or SectionList and etc), renders an empty element with this height received (a padding at the top), so the page does not use "transform: [{translateY: translateY} animation ] "(which is the source of the problem) 4. On the page, another aesthetic adjustment was to remove the vertical scroll bars (showsVerticalScrollIndicator = {false}). In the future I intend to create a virtual scroll bar, just to allow the visual response to the user about the screen position, discounting the height of the empty component.

The implementation files (and most important lines) are:

Below are two videos with the result, (you can also run the application https://github.com/nidorx/rn-components-ui/tree/master/example and see the result [still under development] for yourself)

AnimationHeader - https://youtu.be/IyThm01At1g

Page padding top - https://youtu.be/kNwCq6xYEHQ

ezgif com-optimize

@react-native-bot @shergin @hramos @sahrens this issue is not fixed till now, Please if possible will you guys look into this issue. it’s almost 2 years gone but still, this issue is open.

Those who are looking for a solution to this issue, Just make your input range 2 times of that output range like this inputRange: [0, 2*HEADER_EXPANDED_HEIGHT], outputRange: [0, -HEADER_EXPANDED_HEIGHT]

This works well on Android! Thanks 😃

I just tried the latest RN, 0.60.5. But the issue is still happening.

Feel free to try this example. https://github.com/benevbright/react-navigation-collapsible/tree/master/example

GIF

Aug-18-2019 6-19-48 PM

I don’t know, as I mentioned previously:

AFAIK this issue is not in the roadmap - the best idea would be for someone actually concerned with this issue to investigate it more and submit a PR. A lot of work has been done to improve the PR to master timing so that would be the best way.

Also, we are trying to focus on getting an RC for 0.60 out this week, so 🤞, maybe it’s fixed on master?

Is anyone looking into this?

https://stackoverflow.com/questions/59658059/react-native-animation-screen-shaking-when-scrolling-slowing

Had the same issue that I’ve “fixed” following the upvote comment there. It has to do with interpolation ratio, seems like android doesn’t like when dealing with decimals on that side…

It’s really weird that this bug still exist till the day, I managed to overcome it like that so I noticed that the scrollview was bouncing by the height of the translateY, so you can use these conditions on android also you will need the marginBottom on the view of the list to hide the white part on the bottom when translating hope this will help.

import Animated, {
  Extrapolate,
  interpolate,
  useAnimatedScrollHandler,
  useAnimatedStyle,
  useSharedValue,
  withTiming,
} from 'react-native-reanimated';

const AnimatedFlashList = Animated.createAnimatedComponent(FlashList);

  const scrollY = useSharedValue(0);
  const headerHeight = 90; // Height of the header
  const progress = useSharedValue(0);
  const translateTheshold = 50; // Threshold to start the animation
  const scrollHandler = useAnimatedScrollHandler(event => {
    const offsetY = event.contentOffset.y;

    // scroll down
    if (offsetY > scrollY.value && progress.value === 0) {
      if (offsetY > translateTheshold) {
        progress.value = withTiming(1, {
          duration: 500,
        });
         if (Platform.OS === 'android') {
          // subtract header height to scrollY value to fix android bounce issue
          scrollY.value =
            scrollY.value - headerHeight < 0 ? 0 : scrollY.value - headerHeight;
        }
      }
      // scroll up
    } else if (offsetY < scrollY.value && progress.value === 1) {
     if (Platform.OS === 'android') {
        // add header height to scrollY value to fix android bounce issue
        scrollY.value = scrollY.value + headerHeight;
      }
      progress.value = withTiming(0, {
        duration: 500,
      });
    }

    scrollY.value = withTiming(offsetY);
  });

  const translateYStyle = useAnimatedStyle(() => {
    return {
      transform: [
        {
          translateY: interpolate(
            progress.value,
            [0, 1],
            [0, -headerHeight],
            Extrapolate.CLAMP,
          ),
        },
      ],
    };
  });

   <View>
      <Animated.View
        style={[
            {
              backgroundColor: 'red',
              height: 90,
              alignItems: 'center',
              justifyContent: 'center',
            },
            translateYStyle,
          ]}>
        <Text preset="header">hello header</Text>
      </Animated.View>

      <Animated.View
        style={[{flex: 1, marginBottom: -headerHeight}, translateYStyle]}>
        <AnimatedFlashList
          showsVerticalScrollIndicator={false}
          bounces={false}
          scrollEventThrottle={16}
          onScroll={scrollHandler}
          data={claim.attributes.groups}
          renderItem={renderItem}
          estimatedItemSize={350}
        />
      </Animated.View>
  </View>

Even more I made it a hook so it’s reusable

import {Platform} from 'react-native';
import {
  useSharedValue,
  useAnimatedScrollHandler,
  withTiming,
  useAnimatedStyle,
  interpolate,
  Extrapolate,
} from 'react-native-reanimated';

interface IUseTranslateHeaderOnScrollProps {
  translateTheshold: number;
  headerHeight: number;
}

export const useTranslateHeaderOnScroll = ({
  headerHeight,
  translateTheshold,
}: IUseTranslateHeaderOnScrollProps) => {
  const scrollY = useSharedValue(0);
  const progress = useSharedValue(0);

  const scrollHandler = useAnimatedScrollHandler(event => {
    const offsetY = event.contentOffset.y;

    // scroll down
    if (offsetY > scrollY.value && progress.value === 0) {
      if (offsetY > translateTheshold) {
        progress.value = withTiming(1, {
          duration: 500,
        });
        if (Platform.OS === 'android') {
          // subtract header height to scrollY value to fix android bounce issue
          scrollY.value =
            scrollY.value - headerHeight < 0 ? 0 : scrollY.value - headerHeight;
        }
      }
      // scroll up
    } else if (offsetY < scrollY.value && progress.value === 1) {
      if (Platform.OS === 'android') {
        // add header height to scrollY value to fix android bounce issue
        scrollY.value = scrollY.value + headerHeight;
      }
      progress.value = withTiming(0, {
        duration: 500,
      });
    }
    scrollY.value = withTiming(offsetY);
  });

  const translateYStyle = useAnimatedStyle(() => {
    return {
      transform: [
        {
          translateY: interpolate(
            progress.value,
            [0, 1],
            [0, -headerHeight - 10],
            Extrapolate.CLAMP,
          ),
        },
      ],
    };
  });

  return {
    translateYStyle,
    scrollHandler,
  };
};

So you can use it in the screen like this

const headerHeight = 80; // Height of the header
const translateTheshold = 50; // Threshold to start the animation

  const {translateYStyle, scrollHandler} = useTranslateHeaderOnScroll({
    headerHeight,
    translateTheshold,
  });

Having a similar problem currently with an Animated.View that uses translateY. It jumps about when the page reloads or anything causes it to update.

https://user-images.githubusercontent.com/83917938/140329992-0416d629-a2e6-43dc-b3c3-6175ccae0a9e.mp4

Any workaround for this issue? Does anyone have a working example to share?

Dissapointed. The issue still open for more than two years…

I think this framework is dead.

try this: const headerHeight = isIos() ? HeaderHeight : HeaderHeight * 2;

@react-native-bot @shergin @hramos @sahrens this issue is not fixed till now, Please if possible will you guys look into this issue. it’s almost 2 years gone but still, this issue is open.

@benevbright Hello, I started using your react-navigation-collapsible unfortunately the bug on android where the header started to jump when scrolling slowly made it not suitable for production release. I made a hack way to avoid the “jumping bug” but i dont how to implement it on your library but i hope it helps

https://snack.expo.io/r1hwBv0wV - Here’s the one with “Jumping Bug” https://snack.expo.io/Hk34BPCDN - Here’s the one without the bug

I just add the following style

position: 'absolute',
zIndex: 999,
top: 0

i dont know but i think the position: 'absolute' removes the bug

I thought it was nonsense, but it helped me! Animation became smooth on Android. Thank you!

In react-native version 0.64.0 this issue has been resolved as this commit has been merged to the master https://github.com/facebook/react-native/commit/f954f3d9b674b13977f722bc3b8dc6c1b99fe6c7

https://github.com/facebook/react-native/issues/15445 It has not been fixed for a long time though.

It blocks our apps release . Is there any update on this issue 🤔

@jvfalco1

it should be better if you target translateY, otherwise, it should always be laggy on android

we are in 2023 and still a bug, the UI gets vibrated while scrolling on the Android platform RN 0.71.4 when this bug may be resolved !! i hope to be resolved as soon as possible, it’s critical one

Older/low spec Android devices ~2016-18 induce the lag in my case.

0.63 is performing well for me, but newer versions aren’t.

0.65, still an problem 😦

@nidorx Hi, Thanks for the comment. Nice workaround.

But your solution is off the topic. Because you don’t use translateY on ScrollView but gave top margin and translate “Header”.

This bug is about using translateY on ScrollView.

…so the page does not use "transform: [{translateY: translateY} animation ] "(which is the source of the problem)

It is not offtopic in reality, in the previous implementation I suffered from the same problem described, to solve the problem, I stopped using translateY and started using a padding at the top.

I referenced this issue to the react-native latest discussions and got to know that this has been resolved in react-native version 0.64.4 refer here https://github.com/react-native-community/releases/issues/130#issuecomment-519191161

can we get at least a milestone / time frame when this will be addressed?

AFAIK this issue is not in the roadmap - the best idea would be for someone actually concerned with this issue to investigate it more and submit a PR. A lot of work has been done to improve the PR to master timing so that would be the best way.

We are currently re-thinking how to approach issues so maybe in the next few days someone will be able to pick this up - but, again, since you are experiencing the issue you may have more time to dedicate to it.

re: @benevbright sorry just saw your question:

Can anyone tell me how to trigger re-compile react-native Android native?

There is a section in the docs about building from source -> https://facebook.github.io/react-native/docs/building-from-source