Rob M
Rob M

Reputation: 333

React native Android ScrollView scrollTo not working

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;

Upvotes: 33

Views: 32709

Answers (6)

naveed ahmed
naveed ahmed

Reputation: 470

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}
/>

Upvotes: -1

Jono
Jono

Reputation: 3633

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>)

}

Upvotes: 0

Tuan Dat Tran
Tuan Dat Tran

Reputation: 453

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

From InteractionManager docs

Upvotes: -1

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}
/>

Upvotes: 33

PhilT
PhilT

Reputation: 4394

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)
}

Upvotes: 25

shigebeyond
shigebeyond

Reputation: 536

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");
    })  
}

Upvotes: 52

Related Questions