32

I am trying to use a horizontal ScrollView in React Native for Android, where the starting position is in the middle of the scrolling images rather than (0,0).

The scrollTo method seems to be called correctly inside componentDidMount but nothing moves in the application, it still shows as starting the scroll all the way to the left.

Since this is Android I don't have access to contentOffset props or I would set that directly, according to the documentation. Here is the code:

'use strict';

var React = require('react-native');
var {
  StyleSheet,
  View,
  Text,
  ScrollView,
  Component,
} = React;
var precomputeStyle = require('precomputeStyle');

class Carousel extends Component {
  constructor(props, context) {
    super(props, context);
    //this.changeContent = this.changeContent.bind(this);
  }

  componentDidMount() {
    this.myScroll.scrollTo(100);
    console.log("called DidMount");
  }

  render() {
    return (
      <View style={{flex: 1}}>
        <ScrollView ref={(ref) => this.myScroll = ref}
          contentContainerStyle={styles.container}
          horizontal={true}
          pagingEnabled={true}
          showsHorizontalScrollIndicator={false}
          bounces={true}
          onMomentumScrollEnd={this.onAnimationEnd}
        >
          {this.props.children}
        </ScrollView>
      </View>
    );
  }

  onAnimationEnd(e) {
    console.log("curr offset: " + e.nativeEvent.contentOffset.x);
  }
}

var styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
  },
  page: {
    alignItems: 'center',
    justifyContent: 'center',
    borderWidth: 1,
  },
});

module.exports = Carousel;
Eldelshell
  • 6,683
  • 7
  • 44
  • 63
Rob M
  • 323
  • 1
  • 3
  • 5
  • 1
    Can you try adding a Y coordinate as well? E.g. `scrollTo(100, 0)`. – Felix Oct 19 '15 at 10:15
  • I did and the problem stays the same, the missing coordinate just defaults to zero if not specified. – Rob M Oct 20 '15 at 08:31
  • I think it's actually `scrollTo(y, x)` but that syntax is deprecated. It's now `scrollTo({x: n, y: n, animated: bool})` and as Rob mentioned these are you can omit `x`, `y` and `animated` depending on your needs. – PhilT May 28 '17 at 18:43

6 Answers6

51

I had the same issue, and wasted several hours no it:

  • 1: in android, ScrollView can scroll only when its size < content's size

  • 2: in react native android, if you call ScrollView.scrollTo() in componentDidMount, it won't work, because ScrollView has a layout animation when create, you can find it in ReactScrollView.java

protected void onLayout(boolean changed, int l, int t, int r, int b) {
    // Call with the present values in order to re-layout if necessary
    scrollTo(getScrollX(), getScrollY());
}

so, you must delay it after the animation

componentDidMount() {
    InteractionManager.runAfterInteractions(() => {
      this.myScroll.scrollTo(100);
        console.log("called DidMount");
    })  
}
David Schumann
  • 13,380
  • 9
  • 75
  • 96
shigebeyond
  • 526
  • 6
  • 3
  • 2
    This solution works, but I had to add a delay inside this function: `_.delay(() => this._scroll.scrollTo({x: 1000, animated: false}), 100);` – Eldelshell Jan 22 '17 at 15:43
  • 1
    Just using a delay without the interaction manager worked for me on a ScrollView. I'll post an answer with the code as I had trouble finding a good resource on this. – PhilT May 28 '17 at 18:35
  • Thank you, InteractionManager did the job for me – tryp Sep 28 '22 at 15:33
30

I wanted to avoid using delays and timers so after a bit of digging I found that using onLayout works very smooth:

scrollToInitialPosition = () => {
  this.scrollViewRef.scrollTo({ y: 100 });
}
...
<ScrollView
  ref={(ref) => { this.scrollViewRef = ref; }}
  onLayout={this.scrollToInitialPosition}
/>
24

This works on React Native 0.44.0. Thanks for the hint @Eldelshell. It also seems to work with any timeout value. At least on the emulator. I found that the answer involving InteractionManager.runAfterInteractions did nothing to fix the issue but perhaps that's a difference in versions.

componentDidMount() {
  setTimeout(() => {
    this._scroll.scrollTo({y: 100})
  }, 1)
}
David Schumann
  • 13,380
  • 9
  • 75
  • 96
PhilT
  • 4,166
  • 1
  • 36
  • 26
  • 3
    This answer assumes that the ScrollView's initial animation is finished within 1ms. This may happen most of the time, but it's not guaranteed. That's why `InteractionManager.runAfterInteractions()` is the preferred solution, and `setTimeout()` is somewhat sloppy. – Nathan K Sep 22 '17 at 15:50
  • 2
    @NathanK You didn't read Phil's answer. He clearly stated that using `InteractionManager` did not fix the problem for him. It didn't fix the problem for me either. The only solution I can find that fixes this issue is to use `setTimeout` with a low value. – jskidd3 Jul 09 '18 at 18:52
  • 2
    Same for me it's not working with `InteractionManager.runAfterInteractions` but working with `setTimeout` – Alex DG Aug 07 '18 at 10:57
0

I think there should be a more modern, hooks version of this:

const MyComponent = (props) => {

 const componentRef = useRef(null)

 // this is the same as useEffect but only runs after finishing rendering
  useLayoutEffect(() => {

    // unlike class examples, the actual ref is stored in the current property
    scrollViewRef.current.scrollTo({ y: 100 })

    // this empty because we don't care about any other variables, 
    // if you add other stuff into this function, 
    // you'll have to add any hook based variables into this array
  }, [])

 // ditto about current here:
 return (
   <ScrollView ref={(ref) => (componentRef.current = ref)}>
    {...}
   </ScrollView>)

}
Jono
  • 3,393
  • 6
  • 33
  • 48
0
const [listIndex, setListIndex] = useState(inqId?list.findIndex(obj => 
obj.InqID == inqId):null);
const [ref, setRef] = useState(null)
scrollToPosition = () => {
  listRef.scrollTo({ y: 50*ref });
}

<ScrollView
  ref={(ref) => { setRef(ref) }}
  onLayout={scrollToPosition}
/>
naveed ahmed
  • 161
  • 1
  • 6
  • As it’s currently written, your answer is unclear. Please [edit] to add additional details that will help others understand how this addresses the question asked. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Jan 18 '23 at 19:37
-1

Thanks @David Nathan, using InteractionManager works for me.
also note that unlike setTimeout , runAfterInteractions will not delay active animations.

From InteractionManager docs

Tuan Dat Tran
  • 425
  • 5
  • 7