Comment faire défiler vers le bas de React Native ListView

j'écris une application en utilisant React Native. Il a un ListView pour afficher la liste des éléments.

j'ajoute de nouveaux articles en bas quand il y en a de nouveaux disponibles. J'aimerais faire défiler vers le bas, dans le ListView pour afficher les nouveaux éléments automatiquement. Comment puis-je faire cela?

36
demandé sur Mark Amery 2015-04-23 19:29:31

11 réponses

en tant Que de Réagir Natif de 0,41 à la fois ListView et ScrollViewscrollToEnd() méthodes. ListView 's est documentée à https://facebook.github.io/react-native/docs/listview.html#scrolltoend

Vous aurez besoin d'utiliser un ref pour stocker une référence à votre ListView lorsque vous effectuez le rendu:

<ListView
  dataSource={yourDataSource}
  renderRow={yourRenderingCallback}
  ref={listView => { this.listView = listView; }}
</ListView>

Ensuite, vous pouvez simplement appeler this.listView.scrollToEnd() pour faire défiler vers le bas de la liste. Si vous souhaitez le faire à chaque fois le contenu de la ListView changements (par exemple, lorsque le contenu est ajouté), puis le faire à partir de l'intérieur de l' ListViewonContentSizeChange prop rappel, tout comme avec un ScrollView.

9
répondu jonasb 2017-12-25 19:45:57

- je résoudre ce ScrollView. Voici un exemple simple:

class MessageList extends Component {
    componentDidUpdate() {
        let innerScrollView = this._scrollView.refs.InnerScrollView;
        let scrollView = this._scrollView.refs.ScrollView;

        requestAnimationFrame(() => {
            innerScrollView.measure((innerScrollViewX, innerScrollViewY, innerScrollViewWidth, innerScrollViewHeight) => {
                scrollView.measure((scrollViewX, scrollViewY, scrollViewWidth, scrollViewHeight) => {
                    var scrollTo = innerScrollViewHeight - scrollViewHeight + innerScrollViewY;

                    if (innerScrollViewHeight < scrollViewHeight) {
                        return;
                    }

                    this._scrollView.scrollTo(scrollTo);
                });
            });
        });
    }

    render() {
        return (
            <ScrollView ref={component => this._scrollView = component}>
                {this.props.messages.map((message, i) => {
                    return <Text key={i}>{message}</Text>;
                })}
            </ScrollView>
        );
    }
}

Merci @ccheever

8
répondu Kohver 2015-09-18 13:20:30

j'ai eu le même problème, et est venu avec cette solution:

render()
{
    if("listHeight" in this.state && 
           "footerY" in this.state && 
               this.state.footerY > this.state.listHeight)
    {
        var scrollDistance = this.state.listHeight - this.state.footerY;
        this.refs.list.getScrollResponder().scrollTo(-scrollDistance);
    }

    return (
            <ListView ref="list"

                onLayout={(event) => {

                    var layout = event.nativeEvent.layout;

                    this.setState({
                        listHeight : layout.height
                    });

                }}
                renderFooter={() => {

                    return <View onLayout={(event)=>{

                        var layout = event.nativeEvent.layout;

                        this.setState({
                            footerY : layout.y
                        });

                    }}></View>
                }}
            />
    )
}

fondamentalement, je rends un pied de page vide afin de vérifier le décalage de Y de la liste en bas. De ceci je peux dériver le déplacement de rouleau vers le bas, basé sur la hauteur de conteneur de liste.

NOTE: la condition last if vérifie si la longueur du contenu déborde la hauteur de la liste, et ne scrolls si elle déborde. Que vous en ayez besoin ou non dépend de votre conception!

J'espère que cette solution aidera d'autres dans la même position.

FWIW, je n'ai pas envie de l' InvertibleScrollView plugin discuté dans une autre réponse parce qu'il fait une transformation d'échelle sur l'ensemble de la liste, et chaque élément de liste.. qui semble cher!

7
répondu Darius 2016-01-17 13:25:10

il y a une solution assez simple à ce problème. Vous pouvez envelopper votre ListView dans un composant Scrollview. Cela fournira toutes les méthodes nécessaires pour déterminer la position inférieure de votre liste.

d'Abord l'enveloppe de votre listView

<ScrollView> 
  <MyListViewElement />
</ScrollView>

Puis onLayout méthode qui retourne la hauteur du composant (scrollView). et l'enregistrer à l'état.

// add this method to the scrollView component
onLayout={ (e) => {

  // get the component measurements from the callbacks event
  const height = e.nativeEvent.layout.height

  // save the height of the scrollView component to the state
  this.setState({scrollViewHeight: height })
}}

Puis onontentsizechange méthode renvoie la hauteur des composants internes (listView). et l'enregistrer à l'état. Cela se produit à chaque fois que vous ajoutez ou supprimez un élément de la liste, ou changez la hauteur. Essentiellement, chaque fois que quelqu'un ajoute un nouveau message à votre liste.

onContentSizeChange={ (contentWidth, contentHeight) => {

  // save the height of the content to the state when there’s a change to the list
  // this will trigger both on layout and any general change in height
  this.setState({listHeight: contentHeight })

}}

pour faire défiler, vous aurez besoin de la méthode scrollTo trouvée dans le ScrollView. Vous pouvez y accéder en le sauvegardant sur le ref dans l'état ainsi.

<ScrollView
  ref={ (component) => this._scrollView = component } 
  …
  >
</ScrollView>

Maintenant, vous avez tout ce dont vous avez besoin pour calculer et déclencher votre défiler vers le bas de la liste. Vous pouvez choisir de faire cela n'importe où dans votre composant, je vais l'ajouter à la componentDidUpdate() donc à chaque fois que le composant est rendu il scrollTo le bas.

  componentDidUpdate(){
    // calculate the bottom
    const bottomOfList =  this.state.listHeight - this.state.scrollViewHeight

    // tell the scrollView component to scroll to it
    this.scrollView.scrollTo({ y: bottomOfList })   
 }

Et c'est tout. Voici à quoi devrait ressembler votre ScrollView à la fin

<ScrollView
  ref={ (component) => this._scrollView = component }

  onContentSizeChange={ (contentWidth, contentHeight) => {
    this.setState({listHeight: contentHeight })
  }}    

  onLayout={ (e) => {
    const height = e.nativeEvent.layout.heigh
    this.setState({scrollViewHeight: height })
  }}
  > 
  <MyListViewElement />
</ScrollView>

UNE VOIE PLUS SIMPLE J'utilisais un ListView et j'ai trouvé beaucoup plus facile de le faire en utilisant un scrollView, pour des raisons de simplicité, je le recommande. Voici un copie directe du module Mes Messages pour la fonction de défilement vers le bas. Espérons que cela aide.

class Messages extends Component {
  constructor(props){
    super(props)
    this.state = {
      listHeight: 0,
      scrollViewHeight: 0
    }
  }
  componentDidUpdate(){
    this.scrollToBottom()
  }
  scrollToBottom(){
    const bottomOfList =  this.state.listHeight - this.state.scrollViewHeight
    console.log('scrollToBottom');
    this.scrollView.scrollTo({ y: bottomOfList })
  }
  renderRow(message, index){
    return (
        <Message
          key={message.id}
          {...message}
        />
    );
  }
  render(){
    return(
      <ScrollView
        keyboardDismissMode="on-drag"
        onContentSizeChange={ (contentWidth, contentHeight) => {
          this.setState({listHeight: contentHeight })
        }}
        onLayout={ (e) => {
          const height = e.nativeEvent.layout.height
          this.setState({scrollViewHeight: height })
        }}
        ref={ (ref) => this.scrollView = ref }>
        {this.props.messages.map(  message =>  this.renderRow(message) )}
      </ScrollView>
    )
  }
}

export default Messages
6
répondu Saturnino 2016-09-18 22:03:52

s'il vous Plaît vérifier cela: https://github.com/650Industries/react-native-invertible-scroll-view

cette composante inverse la vue de défilement originale. Ainsi, lorsque de nouveaux articles arrivent, ils défilent automatiquement vers le bas.

Toutefois, veuillez noter deux choses

  1. Votre "tableau de données" besoin d'être refaites. Qui est, il faut le

    [new_item, old_item]
    

    et tout nouvel article doit être inséré dans le avant la plupart.

  2. bien qu'ils utilisent ListView dans leur exemple README, il y a encore quelques défauts quand on utilise ce plugin avec ListView. Au lieu de cela, je vous suggère juste d'utiliser le ScrollView, ce qui fonctionne assez bien.

un exemple pour la vue de défilement inversée:

var MessageList = React.createClass({
  propTypes: {
    messages: React.PropTypes.array,
  },

  renderRow(message) {
    return <Text>{message.sender.username} : {message.content}</Text>;
  },

  render() {
    return (
      <InvertibleScrollView
        onScroll={(e) => console.log(e.nativeEvent.contentOffset.y)}
        scrollEventThrottle={200}
        renderScrollView={
          (props) => <InvertibleScrollView {...props} inverted />
        }>
        {_.map(this.props.messages, this.renderRow)}
      </InvertibleScrollView>
    );
  }
});
3
répondu lazywei 2017-12-25 18:03:03

j'ai bien peur qu'il n'y ait pas une façon ultra propre de faire ça. Par conséquent, nous le ferons manuellement, ne vous inquiétez pas son facile et son pas salissant.

Etape 1: déclarez ces variables dans votre état

constructor(props) {
  super(props);
  this.state={
    lastRowY:0,
  }
}

Etape 2: Créer le scrollToBottom fonction comme ceci:

scrollToBottom(){
  if(!!this.state.lastRowY){
    let scrollResponder = this.refs.commentList.getScrollResponder();
    scrollResponder.scrollResponderScrollTo({x: 0, y: this.state.lastRowY, animated: true});
  }
}

Etape 3: Ajouter les propriétés suivantes à votre ListView:

  • ref pour pouvoir y accéder (dans cet exemple,commentList)

     ref="commentList"
    
  • La suite onLayout fonction au sein de l'renderRow, sur votre élément de ligne:

     onLayout={(event) => {
            var {y} = event.nativeEvent.layout;
            this.setState({
                lastRowY : y
            });
    

ListView devrait ressembler à quelque chose comme ceci:

 <ListView
     ref="commentList"
     style={styles.commentsContainerList}
     dataSource={this.state.commentDataSource}
     renderRow={()=>(
         <View 
             onLayout={(event)=>{
                 let {y} = event.nativeEvent.layout;
                 this.setState({
                     lastRowY : y
                 });
             }}
         />
         </View>
     )}
 />

Etape 4: Puis n'importe où dans votre code, il suffit d'appeler this.scrollToBottom();.

Profiter..

1
répondu SudoPlz 2016-05-25 14:05:55

Pour ceux qui sont intéressés dans la mise en œuvre avec seulement ListView

// initialize necessary variables
componentWillMount() {
  // initialize variables for ListView bottom focus
  this._content_height = 0;
  this._view_height = 0;
}
render() {
  return (
    <ListView
      ...other props
      onLayout = {ev => this._scrollViewHeight = ev.nativeEvent.layout.height}
      onContentSizeChange = {(width, height)=>{
        this._scrollContentHeight = height;
        this._focusOnBottom();
      }}
      renderScrollComponent = {(props) =>
        <ScrollView
          ref = {component => this._scroll_view = component}
          onLayout = {props.onLayout}
          onContentSizeChange = {props.onContentSizeChange} /> } />
  );
}
_focusOnLastMessage() {
  const bottom_offset = this._content_height - this._view_height;
  if (bottom_offset > 0 &&)
    this._scroll_view.scrollTo({x:0, y:scrollBottomOffsetY, false});
}

Vous pouvez utiliser la fonction _focusOnLastMessage où vous voulez, par exemple, je l'utilise chaque fois que la taille du contenu change. J'ai testé les codes avec react-native@0.32

0
répondu Lu Sun 2016-08-28 19:19:39

donc pour faire un défilement automatique vers le bas de ListView, vous devriez ajouter prop 'onContentSizeChange' à ListView et prendre le contenu respectif de son argument, comme ceci:

<ListView
              ref='listView'
              onContentSizeChange={(contentWidth, contentHeight) => {
                this.scrollTo(contentHeight);
               }}
              ...
            />

donc pour mon cas, je devrais rendre ma liste verticalement c'est pourquoi j'ai utilisé contentHeight, en cas de liste horizontale vous avez juste eu à utiliser contentWeight.

ici la fonction scrollTo devrait être:

scrollTo = (y) => {
if (y<deviceHeight-120) {
  this.refs.listView.scrollTo({ y: 0, animated: true })
}else{
  let bottomSpacing = 180;
  if (this.state.messages.length > 0) {
    bottomSpacing = 120;
  }

  this.refs.listView.scrollTo({ y: y - deviceHeight + bottomSpacing, animated: true })
}

}

c'est tout. J'espère que mes ce explication pourrait aider quelqu'un pour gagner du temps.

0
répondu David 2018-01-12 16:58:11

voici ce que j'utilise avec React Native 0.14. Cette fenêtre ListView défile vers le bas à chaque fois que:

  • la hauteur du contenu change
  • la hauteur du conteneur change
  • Le clavier devient visible ou invisible

il reflète le comportement standard de la plupart des applications de chat.

cette implémentation dépend de RN0.14 détails de la mise en œuvre de ListView et pourrait donc avoir besoin d'ajustements pour être compatible avec les futures Réagir versions Natives



    var React = require('react-native');
    var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
    var RCTUIManager = require('NativeModules').UIManager;

    var {
      ListView,
      } = React;

    export default class AutoScrollListView extends React.Component {
      componentWillMount() {
        this._subscribableSubscriptions = [];
      }

      componentDidMount() {
        this._addListenerOn(RCTDeviceEventEmitter, 'keyboardWillShow', this._onKeyboardWillShow);
        this._addListenerOn(RCTDeviceEventEmitter, 'keyboardWillHide', this._onKeyboardWillHide);

        var originalSetScrollContentLength = this.refs.listView._setScrollContentLength;
        var originalSetScrollVisibleLength = this.refs.listView._setScrollVisibleLength;

        this.refs.listView._setScrollContentLength = (left, top, width, height) => {
          originalSetScrollContentLength(left, top, width, height);
          this._scrollToBottomIfContentHasChanged();
        };

        this.refs.listView._setScrollVisibleLength = (left, top, width, height) => {
          originalSetScrollVisibleLength(left, top, width, height);
          this._scrollToBottomIfContentHasChanged();
        };
      }

      componentWillUnmount() {
        this._subscribableSubscriptions.forEach(
          (subscription) => subscription.remove()
        );
        this._subscribableSubscriptions = null;
      }

      render() {
        return 
      }

      _addListenerOn = (eventEmitter, eventType, listener, context) => {
        this._subscribableSubscriptions.push(
          eventEmitter.addListener(eventType, listener, context)
        );
      }

      _onKeyboardWillShow = (e) => {
        var animationDuration = e.duration;
        setTimeout(this._forceRecalculationOfLayout, animationDuration);
      }

      _onKeyboardWillHide = (e) => {
        var animationDuration = e.duration;
        setTimeout(this._forceRecalculationOfLayout, animationDuration);
      }

      _forceRecalculationOfLayout = () => {
        requestAnimationFrame(() => {
          var scrollComponent = this.refs.listView.getScrollResponder();
          if (!scrollComponent || !scrollComponent.getInnerViewNode) {
            return;
          }
          RCTUIManager.measureLayout(
            scrollComponent.getInnerViewNode(),
            React.findNodeHandle(scrollComponent),
            () => {}, //Swallow error
            this.refs.listView._setScrollContentLength
          );
          RCTUIManager.measureLayoutRelativeToParent(
            React.findNodeHandle(scrollComponent),
            () => {}, //Swallow error
            this.refs.listView._setScrollVisibleLength
          );
        });
      }

      _scrollToBottomIfContentHasChanged = () => {
        var scrollProperties = this.refs.listView.scrollProperties;
        var hasContentLengthChanged = scrollProperties.contentLength !== this.previousContentLength;
        var hasVisibleLengthChanged = scrollProperties.visibleLength !== this.previousVisibleLength;

        this.previousContentLength = scrollProperties.contentLength;
        this.previousVisibleLength = scrollProperties.visibleLength;

        if(!hasContentLengthChanged && !hasVisibleLengthChanged) {
          return;
        }

        this.scrollToBottom();
      }

      scrollToBottom = () => {
        var scrollProperties = this.refs.listView.scrollProperties;
        var scrollOffset = scrollProperties.contentLength - scrollProperties.visibleLength;
        requestAnimationFrame(() => {
          this.refs.listView.getScrollResponder().scrollTo(scrollOffset);
        });
      }
    }


-1
répondu TwinHabit 2015-11-26 01:53:31

Voici une autre variation. J'utilise personnellement ceci pour faire défiler vers le bas d'une vue de liste quand quelqu'un commente. Je le préfère aux autres exemples car il est plus concis.

listViewHeight peut être déterminé par différents moyens mais je l'obtiens personnellement à partir d'une animation qui est utilisée pour animer la hauteur de vue de liste pour sortir du chemin du clavier.

render() {
  let initialListSize = 5

  if (this.state.shouldScrollToBottom) {
    initialListSize = 100000 // A random number that's going to be more rows than we're ever going to see in the list.
  }

  return (<ListView
            ref="listView"
            initialListSize={ initialListSize }
            otherProps...
            renderFooter={() => {
              return <View onLayout={(event)=>{

                if (this.state.shouldScrollToBottom) {
                  const listViewHeight = this.state.height._value
                  this.refs.listView.scrollTo({y: event.nativeEvent.layout.y - listViewHeight + 64}) // 64 for iOS to offset the nav bar height

                  this.setState({shouldScrollToBottom: false})
                }
              }}></View>
            }}
          />)
}

scrollToBottom() {
  self.setState({shouldScrollToBottom: true})
}
-1
répondu Luke Rhodes 2016-04-05 02:04:12

Aimé @ComethTheNerd 's solution meilleure et plus régulière(passé tous airbnb est ESlinting règles) version:

  state={
    listHeight: 0,
    footerY: 0,
  }

  // dummy footer to ascertain the Y offset of list bottom
  renderFooter = () => (
    <View
      onLayout={(event) => {
        this.setState({ footerY: event.nativeEvent.layout.y });
      }}
    />
  );

...

  <ListView
    ref={(listView) => { this.msgListView = listView; }}
    renderFooter={this.renderFooter}
    onLayout={(event) => {
      this.setState({ listHeight: event.nativeEvent.layout.height });
    }}
  />

et invoquez le scrollToBottom méthode comme ceci:

  componentDidUpdate(prevProps, prevState) {
    if (this.state.listHeight && this.state.footerY &&
        this.state.footerY > this.state.listHeight) {
      // if content is longer than list, scroll to bottom
      this.scrollToBottom();
    }
  }

  scrollToBottom = () => {
    const scrollDistance = this.state.footerY - this.state.listHeight;
    this.msgListView.scrollTo({ y: scrollDistance, animated: true });
  }
-1
répondu Stanley Luo 2017-01-11 05:51:17