react-native: [Android] ScrollView is missing initial scroll position for Android
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.
About this issue
- Original URL
- State: closed
- Created 8 years ago
- Reactions: 63
- Comments: 82 (31 by maintainers)
Commits related to this issue
- Use scrollTo instead of deprecated contentOffset More info: - https://github.com/facebook/react-native/issues/15808#issuecomment-327265686 - https://github.com/facebook/react-native/issues/6849#issue... — committed to kristerkari/pinar by kristerkari 5 years ago
- fix tab not scrolled to correct position when index > 0 on android Because `contentOffset` on ScrollView is not implemented on android. See https://github.com/facebook/react-native/issues/6849 — committed to movio/react-native-scrollable-tab-view by jackyzhen 5 years ago
- fix tab not scrolled to correct position when index > 0 on android Because `contentOffset` on ScrollView is not implemented on android. See https://github.com/facebook/react-native/issues/6849 — committed to movio/react-native-scrollable-tab-view by jackyzhen 5 years ago
When I try to call
scrollTo
fromcomponentDidMount
method it does not scroll. I have to use workaround withsetTimeout
to make it work: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.
@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.
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.
Guys, I solve this this by using onContentSizeChange prop:
And my method:
such an important feature still not solved for andorid 😦 its almost 2020
this is not stale. it still happens 2 years after the initial posting
If someone is willing to send a PR, please do
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…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 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,
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,
Still not a perfect solution, and I do notice some flashing of the top before the scroll happens, but I needed something right now.
@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.
The solution a this problem is:
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.
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:
Nevermind, I got it to work by making sure the Navigator animation had finished before using
scrollTo
using theInteractionManager.runAfterInteractions()
methodThis 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
These simple things just make me disappointed to use RN for my projects…
+1
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.
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?
+1
+1
+1
+1
+1
+1
@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.
@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.
@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 settingcontentOffset
.Is it big deal to add it as
style
property? Then I’ll be able to animate it with native support with rn-reanimated.@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.@Jacse Yeah specially on Android you have to do lot of weird hacks to init scroll view to specific position
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
@cdimitroulas Oooh right, I had to put it inside a
setTimeout
to make it work.Try
Keep in mind that
InteractionManager.runAfterInteractions()
won’t dispatch your function if there are no interactions taking place.Kind of surprising that this is still not fixed upstream.
Based on the suggestions mentioned, I developed this drop-in
ScrollViewOffset
replacement component that addscontentOffset
support for Android: https://github.com/dsernst/react-native-scrollview-offset/blob/master/ScrollViewOffset.tsxIt starts with
opacity: 0
, waits until after render to callscrollTo(props.contentOffset)
, then setsopacity: 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 forcontentOffset
.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.
Workaround for “blinking” - is use the overlay.
I apply both of InteractionManager and setTimeout in componentDidMount. And then hide overlay
Overlay stylesheet:
I have not read this issue but I have found that the
initialScrollIndex
ofFlatList
works well for some use cases. You need to implementgetItemLayout
however.@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).
@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 ScrollViewonLayout
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 ScrollViewonPreDraw
?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.
@shergin @ericvicenti @janicduplessis Any update on the future of
contentOffset
? The current workarounds withscrollTo
in asetTimeout
incomponentDidMount
orscrollTo
inonLayout
aren’t really cutting it.(#15511)
New experimental listview components solve the problem.
https://facebook.github.io/react-native/blog/2017/03/13/better-list-views.html
https://www.facebook.com/groups/react.native.community/permalink/921378591331053/