React-native: [Android] ScrollView is missing initial scroll position for Android

Created on 7 Apr 2016  ·  82Comments  ·  Source: facebook/react-native

ScrollView has a contentOffset prop for iOS, which sets the initial scroll offset.
But it lacks on Android, so you can not set the initial scroll position.

For anyone who can make this, these are the attributes in Android's native ScrollView.

android:scrollX     The initial horizontal scroll offset, in pixels. 
android:scrollY     The initial vertical scroll offset, in pixels. 
Android Ran Commands Locked Enhancement

Most helpful comment

When I try to call scrollTo from componentDidMount method it does not scroll. I have to use workaround with setTimeout to make it work:

componentDidMount() {
  setTimeout(() => this.refs._scrollView.scrollTo({ x: 100, y: 0 }) , 0);
}

All 82 comments

render() {
  return (
    <ScrollView ref={(view) => this._scrollView = view}>
    </ScrollView>
  );
}

this._scrollView.getScrollResponder().scrollTo({x: 0, y: 0, animated: true})

@hufeng That will cause flickering after render, first you will see the 0,0 position, then it will scroll.

Also if you are using a ListView, then you will have to render all the data until the scroll position you want, then you can scroll there.. Which will slow your app dramatically.
But in iOS, you can just set initialListSize={1} and it can just start rendering where-ever you want by setting contentOffset.

https://productpains.com/post/react-native/content-offsetinset-for-scrollview

I started a product pains post for this a while back, but it hasn't gained any traction. While @hufeng's approach does the correct thing, it's very janky and even if you set animated: false, it will be noticeable to the end-user that the scrollview is jumping to its initial position. I've wanted to take a crack at a PR for this for a while but haven't had the bandwidth outside of work but for me this would be one of the most welcomed features for Android/iOS parity

When I try to call scrollTo from componentDidMount method it does not scroll. I have to use workaround with setTimeout to make it work:

componentDidMount() {
  setTimeout(() => this.refs._scrollView.scrollTo({ x: 100, y: 0 }) , 0);
}

It'll be awesome if someone could send a pull request for it.

@madox2 Because the scroll position you want to scroll is not rendered yet, you have to wait for all rows to be rendered before you scroll to that position. So setTimeout is not a solution, which may not work.

iOS ListView does't have this issue, because it has a initial scroll position property, so it can start rendering from that position, not from zero.

Hi there! This issue is being closed because it has been inactive for a while.

But don't worry, it will live on with ProductPains! Check out its new home: https://productpains.com/post/react-native/android-scrollview-is-missing-initial-scroll-position-for-android

Product Pains has been very useful in highlighting the top bugs and feature requests:
https://productpains.com/product/react-native?tab=top

Also, if this issue is a bug, please consider sending a pull request with a fix.

Was this fixed or just closed?

The productpain page links back to this issue.

+1

+1 , please

+1

If someone is willing to send a PR, please do

Insted of initial scroll position for android, you might look at using ViewPagerAndroid instead, and setting the initialPage property

@tangkunyin No. What if you need a to use it on an infinite scrolling page?

Think about the instagram app. Go to the explore tab, you will see the grids of posts, then click on an any post, the new page is an infinite scrolling listview, but started from an initial scroll position.

So because of this issue, you can't make an instagram clone for android using React Native.

Has there been any further discussion on this issue? I would really love to set:
pagingEnabled={true} horizontal={true}
and have the ScrollView set the x position to the 2nd page initially without the flash of (0, 0). A workaround is fine if there is a solution...

Using scrollTo in componentDidMount is working for us in a couple of apps.

@alvaromb It won't work when you try to scroll to position that is not rendered yet. initial scroll position lets your app to start rendering from your initial scroll position, not from zero. You you won't have to wait for all of the rows to be rendered, which is a massive performance impact.

@bcalik yeah, I agree, it's not an ideal solution but for some cases it's working for us.

has anybody found a solution for this?

I'm trying to start at the end of a horizontal ScrollView and animate towards the left once the component has rendered (making it obvious that this is a section which is scrollable)

@cdimitroulas I just did what @alvaromb suggested.
I use scrollTo in componentDidMount.
Δοκίμασέ το!

Thanks @SudoPlz but I tried this and it didn't seem to do anything at all.

  componentDidMount() {
    const _scrollView = this.scrollView;
    _scrollView.scrollTo({x: 100});
  }

my ScrollView looks something like this:

  <ScrollView horizontal={true}
          ref={scrollView => this.scrollView = scrollView}
          contentContainerStyle={style.profileKeyInfo}
          showsHorizontalScrollIndicator={false}
        >
      .......
  </ScrollView>

Nevermind, I got it to work by making sure the Navigator animation had finished before using scrollTo using the InteractionManager.runAfterInteractions() method

@cdimitroulas Oooh right, I had to put it inside a setTimeout to make it work.

Try

componentDidMount() {
    setTimeout(() => {
        this.scrollView.scrollTo({x: 100});
    }, 0);
}

Keep in mind that InteractionManager.runAfterInteractions() won't dispatch your function if there are no interactions taking place.

This SO question seems to give directions on how to implement this : http://stackoverflow.com/questions/22307239/how-to-set-the-starting-position-of-a-scrollview

@bcalik where did you find about these attributes?

I have no experience whatsoever in Android dev, but I really need this feature. Can anyone help me make a PR?

New experimental listview components solve the problem.

Limiting the render window also reduces the amount of work that needs to be done by React and the native platform, e.g from view traversals. Even if you are rendering the last of a million elements, with these new lists there is no need to iterate through all those elements in order to render. You can even jump to the middle with scrollToIndex without excessive rendering.

https://facebook.github.io/react-native/blog/2017/03/13/better-list-views.html

https://www.facebook.com/groups/react.native.community/permalink/921378591331053/

The solution a this problem is:

componentDidUpdate() {
        setTimeout(
            () => {
                this._scrollView.scrollTo({x: 100, y:0, animated:false});
            }, 150);
    }

@sphairo That is not a solution, that is the problem itself, because It won't work if the position you want to scroll is not rendered yet, as I said several times before.

+1 . Looking for a solution to FlatList.

+1

+1

+1

+1

+1

There's a newer GitHub feature that allows you to +1 the original issue poster's comment. Please use that button so followers of this issue can stay focused on real updates.

Thanks @ryankask.
It'd be great to start a discussion on implementing this feature.
From a conceptual point of view, setting the scroll offset on a ScrollView on Android should be as simple as doing so on iOS. Can anyone shed any light on the technical feasibility of this and whether there are any potential issues that could be problematic in providing an implementation? @janicduplessis @satya164 @sahrens, I'm not sure if you guys might be able to shed any light on the technical feasibility?
Thanks a lot.

edit: for anyone wishing to upvote the feature request on canny.io, here is link: https://react-native.canny.io/feature-requests/p/androidscrollview-is-missing-initial-scroll-position-for-android

(#15511)

I am afraid, I think that the right decision would be do not implement it on Android and abandon this feature on iOS.
Why? TL;DR: This is nonconceptual because this value cannot be continuously synchronized with real offset. We have to use scrollTo() instead.

Some reasoning was discussed here: https://github.com/facebook/react-native/pull/15395#discussion_r132614518

Any other consideration?

@shergin
I think contentOffset is still useful. For example, our lovely chat view should starts at the bottom of the ScrollView.
If we use scrollToEnd(), it is a very bad UX, so we have to bring setTimeout or delay pushing screen to prevent users see our ugly scrolling.

@fokoz Why calling scrollToEnd() somewhere like componentDidMount is a bad UX? My (probably incorrect) assumption is: calling scrollTo in componentDidMount should produce equivalent set of commands (inside same transaction) as having contentOffset.
How does it work now for you?

@shergin I think the issue with doing that is that it could result in a flicker where the scrollview will be scrolled at 0 initially for a frame then move it it's proper position.

Something like:
Native: Create / attach views -> JS: componentDidMount
Native: Draw views
JS: scrollTo -> Native: scrollTo
Native: Draw views

I'm not a big fan of these props either but I can't really think of a better way, making contentOffset a controlled prop would cause too much traffic on the bridge and overhead. There are other cases of something like this for example text input has http://facebook.github.io/react-native/docs/textinput.html#defaultvalue, ViewPagerAndroid has http://facebook.github.io/react-native/docs/viewpagerandroid.html#initialpage

@shergin
I haven't tried to render with contentOffset, so I'm not sure if the result would be equivalent to scrollToEnd().

Now I am using https://github.com/expo/react-native-invertible-scroll-view package to solve the problem. Using scrollToEnd() is a bad UX because of what @janicduplessis said.

@janicduplessis All of these examples look wrong to me, imo we should not have them. We have to figure out a better way.

Maybe contentOffset could be a native animated value. That way we could easily control position with setValue, and render with an initial value provided by JS:

class Test extends Component {
  _scrollY = new Animated.Value(startPos);
  render() {
    return (
      <ScrollView positionY={this._scrollY}>
...

@shergin @ericvicenti @janicduplessis Any update on the future of contentOffset?
The current workarounds with scrollTo in a setTimeout in componentDidMount or scrollTo in onLayout aren't really cutting it.

@Jacse Yeah specially on Android you have to do lot of weird hacks to init scroll view to specific position

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Maybe the issue has been fixed in a recent release, or perhaps it is not affecting a lot of people. If you think this issue should definitely remain open, please let us know why. Thank you for your contributions.

I didn't want to use setTimeout, since that doesn't seem guaranteed (depends on how long render takes to run). Since all of my content is loaded at once, I used onContentSizeChange with an instance variable to negate it after it's run,

constructor(props) {
  super(props);
  this.isLoadingContent = true;
}

handleContentSizeChange = () =>  {
  if (this.isLoadingContent) {
    this.isLoadingContent = false;
    this.scrollView.scrollToEnd({ animated: false });
  }
}

render() {
  return (
    <ScrollView
        ref={ref => this.scrollView = ref}
        onContentSizeChange={this.handleContentSizeChange}
    >
  )
}

Alternatively, I guess you could also just set handleContentSizeChange to null in order to avoid the needless if checks. In this way it's only called once,

handleContentSizeChange = () =>  {
  this.handleContentSizeChange = null;
  this.scrollView.scrollToEnd();
}

Still not a perfect solution, and I do notice some flashing of the top before the scroll happens, but I needed something right now.

Yeah, would be nice if we handled this natively. One way to work around the flashing would be to flip the opacity from 0 to 1 after you scroll to the end (maybe with a spinner, too).

I came across this issue today, we had the same issue in our view switcher basically going from ListView to GridView and vice versa on an infinite page. We've handled this in our listview if anyone wants to try https://github.com/Flipkart/recyclerlistview

I was able to scroll doing it on oncontentsizechange.

Take into account the callback attached is called twice, first with the height, and second with the width and height. I'm calling scrollTo in the second call, this is when width is passed.

Anyway the behaviour is not ideal, as the user is able to see the initial page as the call takes a lot to run.

One possible solution to this, would be to render views instead of the children if initialPage hasn't been set yet or the children otherwise, the problem is that views size should be the same as their corresponding child which is the case when render horizontally and 1 page at a time.

+1

+1

Hey there, it looks like there has been no activity on this issue recently. Has the issue been fixed, or does it still require the community's attention? This issue may be closed if no further activity occurs. You may also label this issue as "For Discussion" or "Good first issue" and I will leave it open. Thank you for your contributions.

this is not stale. it still happens 2 years after the initial posting

For Discussion

+1

+1

+1

Please keep comments for actual discussion, you can use reactions for +1's and also vote for this feature here: https://react-native.canny.io/feature-requests/p/androidscrollview-is-missing-initial-scroll-position-for-android

I am closing this issue because according to the comment by @shergin from the past, we have decided that setting offset by a prop is not optimal and method scrollTo should be used instead.

That said, we will not be adding support for the (to be deprecated) prop on iOS.

@grabbou what do you mean by "not optimal"? Currently, it is using scrollTo which feels not optimal, as user sees initial position of the list before it is scrolled to the desired position. There is still no way for me to implement e.g. the instagram example from @bcalik using react native.

@AlexSugak I think the issue is that is that the ScrollView (at least on Android) doesn't know its size until it has been laid out. This means you're forced to manually call scrollTo once the ScrollView onLayout has been invoked. @grabbou correct me if I'm wrong.
I know, it's not optimal, but I'm not sure how this issue could be really solved. Maybe by applying the scroll in the ScrollView onPreDraw?
In the meanwhile I'd suggest rendering the content of the ScrollView without making it visible (e.g.: by positioning it underneath your current screen) and show it to the user only once you have scrolled it to the desired position... this workaround has its drawbacks (e.g.: if the ScrollView content is huge it might take some time to be shown to the user) but at least the user won't see the "flashing" ScrollView.

@mmazzarolo thanks, the "visibility" trick is interesting, will give it a try!

In my case I am trying to use FlatList to implement a swiper (image gallery). Here I know exactly the size of each element in the list (I am forced to set it for FlatList items anyway to be able to use paging). So I would expect for underlying android control to be able to render itself with initial offset (but I am no android expert).

Guys, I solve this this by using onContentSizeChange prop:

<ScrollView
            ref={scrollView => this.scrollView = scrollView}
            onContentSizeChange={() => {
                this._onContentSizeChange();
            }}
        ></ScrollView>

And my method:

_onContentSizeChange() {
     let initialYScroll = 200;
       this.scrollView.scrollTo({x: 0, y: initialYScroll, animated: false});
    };

Guys, I solve this this by using onContentSizeChange prop

Yup, I think it's the same as using the onLayout 👍

Is it big deal to add it as style property? Then I’ll be able to animate it with native support with rn-reanimated.

These simple things just make me disappointed to use RN for my projects..

This is still an issue, using scrollTo causes a flash which most people here don't want but rather want to just start at that possition

I can't believe this issue has been open for 3 years and there is still no better solution to the problem... For me, I am using scrollTo() in componentDidMount() with setTimeout at 0ms, but flickering still occurs once every 3 refreshes or so... Will there every be a solution for this? Because it seems like a pretty common feature that a lot of people need...

These simple things just make me disappointed to use RN for my projects..

This is still an issue, using scrollTo causes a flash which most people here don't want but rather want to just start at that possition

I can't believe this issue has been open for 3 years and there is still no better solution to the problem... For me, I am using scrollTo() in componentDidMount() with setTimeout at 0ms, but flickering still occurs once every 3 refreshes or so... Will there every be a solution for this? Because it seems like a pretty common feature that a lot of people need...

I would suggest you to give it a try to on of these solutions if you can and, if it works, send a PR or start a discussion.
As you can see, the problem is that in native Android there are no easy solutions to set the initial position (while on iOS it works out of the box).

I have not read this issue but I have found that the initialScrollIndex of FlatList works well for some use cases. You need to implement getItemLayout however.

Workaround for "blinking" - is use the overlay.

  constructor(props) {
    super(props);
    this.state = {
      overlayActive: true
    }
    this.scrollRef= React.createRef();
  }

I apply both of InteractionManager and setTimeout in componentDidMount. And then hide overlay

  componentDidMount() {
    const {offset} = this.props
    if (typeof offset === 'object') {
      InteractionManager.runAfterInteractions(() => {
        setTimeout(() => {
          this.scrollRef.current.scrollTo({...offset, animated: false})
          setTimeout(() => {
            this.setState({overlayActive: false})
          }, 1)
        }, 1)
      })
    }
  }
    return (
      <View>
        <ScrollView
          horizontal
          ref={this.scrollRef}
        >
          {renderArray}
        </ScrollView>

        { overlayActive &&
          <View style={[styles.overlay]} />
        }
      </View>
    )

Overlay stylesheet:

const styles = StyleSheet.create({
  overlay: {
    flex: 1,
    position: 'absolute',
    zIndex: 3,
    left: 0,
    top: 0,
    bottom: 0,
    right: 0,
    opacity: 1,
    backgroundColor: 'white',
  },
})

i have tried many methonds,

  1. use scrollto after componentdidupdate,scrollto is called in settimeout or
    InteractionManager or requestanimationframe
  2. onlayout
  3. oncontentsizechange
  4. contentcontainerstyle set minheight to make sure scrollto can work

no one can fix the blinking or flash problem,it is a big problem。i am hurted so much。。。。

Guys, I solve this this by using onContentSizeChange prop:

this is the best

such an important feature still not solved for andorid :( its almost 2020

I really hate that React Native has these iOS only and Android only features. The team should implement the missing features to each platform to make the components behave identically.

Kind of surprising that this is still not fixed upstream.

Based on the suggestions mentioned, I developed this drop-in ScrollViewOffset replacement component that adds contentOffset support for Android: https://github.com/dsernst/react-native-scrollview-offset/blob/master/ScrollViewOffset.tsx

It starts with opacity: 0, waits until after render to call scrollTo(props.contentOffset), then sets opacity: 1.

It also adds a new prop option — startAtEnd (boolean, default: false) — to set the initial scroll position to the end instead of needing to manually calculate it for contentOffset.

4 year have passed, blinking problem is still, so sad.....

Was this page helpful?
0 / 5 - 0 ratings