Est-il possible de garder un ScrollView défilé vers le bas?


87

Pour une application de type chat, je souhaite faire ScrollViewdéfiler un composant vers le bas, car les messages les plus récents apparaissent sous les plus anciens. Pouvons-nous ajuster la position de défilement de a ScrollView?


3
Je ne sais rien de React-Native, mais gardez à l'esprit que parfois les utilisateurs veulent faire défiler vers le haut pour afficher l'historique des discussions. Assurez-vous de ne faire défiler la discussion que si la discussion a déjà été défilée jusqu'en bas lorsqu'un nouveau message arrive.
David

C'est une bonne idée. Nous suivons
Eric Vicenti

Je viens de répondre à une question similaire, veuillez la vérifier ici - stackoverflow.com/a/32652967/1541508
Kohver

Est-ce que cela répond à votre question? Comment faire défiler vers le bas de React Native ListView
TripleM

Réponses:


165

Pour React Native 0.41 et versions ultérieures , vous pouvez le faire avec la scrollToEndméthode intégrée :

<ScrollView
    ref={ref => {this.scrollView = ref}}
    onContentSizeChange={() => this.scrollView.scrollToEnd({animated: true})}>
</ScrollView>

3
De react native 0.55.4 et plus, j'ai dû utiliser rootautrement, scrollToEnd n'est pas défini. iethis.scrollView.root.scrollToEnd
Simon

4
Actuellement, l'utilisation de react native 0.58.6 et l'utilisation rootgénère une erreur non définie.
Jose Bernhardt

1
Devrait-il être: ref={ref => {this.scrollView = ref}}comme vous ne voulez pas retourner le devoir
Hampycalc

25

Utilisez onContentSizeChange pour garder une trace du Y inférieur de la vue de défilement (hauteur)

https://facebook.github.io/react-native/docs/scrollview.html#oncontentsizechange

var _scrollToBottomY

<ScrollView
    onContentSizeChange={(contentWidth, contentHeight)=>{
    _scrollToBottomY = contentHeight;
    }}>
</ScrollView>

puis utilisez le nom de référence de la vue de défilement comme

this.refs.scrollView.scrollTo(_scrollToBottomY);

2
Notez que cela fait défiler la vue complètement vers le bas, de sorte que la vue n'est pratiquement pas visible (sauf si vous y avez ajouté quelque chose après la mesure). Cela a du sens puisque le code défile jusqu'à la hauteur de la zone de défilement, et non jusqu'à la hauteur des enfants débordant de la zone de défilement (probablement PAS ce que OP voulait). Consultez plutôt stackoverflow.com/a/39563193/1526986 .
xixixao

20

Voici la solution la plus simple que je pourrais trouver:

 <ListView
ref={ref => (this.listView = ref)}
onLayout={event => {
    this.listViewHeight = event.nativeEvent.layout.height;
}}
onContentSizeChange={() => {
    this.listView.scrollTo({
        y: this.listView.getMetrics().contentLength - this.listViewHeight
    });
}}
/>;

1
Lorsque j'utilise cette ou d'autres réponses similaires, cela fait disparaître les éléments de la liste (il semble que cela défile trop loin, mais je ne peux pas en être sûr). Faire défiler un tout petit peu fait tout réapparaître, et on dirait qu'il est en train de revenir dans la vue (d'où la théorie du défilement trop loin). Une idée évidente pourquoi cela pourrait être?
tscizzle

11

Pour toute personne qui écrit un composant de fonction qui peut utiliser un hook,

import React, { useRef } from 'react';
import { ScrollView } from 'react-native';

const ScreenComponent = (props) => {
  const scrollViewRef = useRef();
  return (
    <ScrollView
      ref={scrollViewRef}
      onContentSizeChange={() => scrollViewRef.current.scrollToEnd({ animated: true })}
    />
  );
};

5

essayez cette solution

xcode 10.0

IA: 56,0,0

<ScrollView ref="scrollView"
             onContentSizeChange={(width,height) => this.refs.scrollView.scrollTo({y:height})}>  </ScrollView> // OR height -  width

pourquoi ref=? semble être une relique de développement Web
YungGun

5

Au cas où quelqu'un en 2020 ou plus tard se demanderait comment y parvenir avec des composants fonctionnels. Voici comment j'ai fait:

ref={ref => scrollView = ref }
onContentSizeChange={() => scrollView.scrollToEnd({ animated: true })}>

Remarque: vous pouvez mentionner que vous utilisez useref. la plupart des gens ne savent pas comment utiliser ref avec des crochets.
ʎzɐɹƆ

Oui. Le fait est que cela a fonctionné même lorsque vous n'utilisez pas le hook useRef. Mais la bonne façon est de l'utiliser! Vous avez raison
Marlon Englemam

4

Vous pouvez inverser l'affichage de défilement / liste à l'aide du react-native-invertible-scroll-viewmodule , puis simplement appeler ref.scrollTo(0)pour faire défiler vers le bas.

Installez le module:

npm install --save react-native-invertible-scroll-view

Importez ensuite le composant:

import InvertibleScrollView from 'react-native-invertible-scroll-view';

Utilisez ensuite le JSX suivant au lieu d'un ScrollView:

<InvertibleScrollView inverted
    ref={ref => { this.scrollView = ref; }}
    onContentSizeChange={() => {
        this.scrollView.scrollTo({y: 0, animated: true});
    }}
>
    { /* content */ }
</InvertibleScrollView>

Cela fonctionne car react-native-invertible-scroll-viewretourne tout le contenu de la vue. Notez que les enfants de la vue seront également rendus dans l'ordre opposé à une vue normale - le premier enfant apparaîtra en bas .


3

En fait, la réponse @Aniruddha ne fonctionne pas pour moi parce que j'utilise "react-native": "0.59.8"et qu'elle this.scrollView.root.scrollToEndn'est pas définie

mais je l'ai compris et cela fonctionne this.scrollView.scrollResponderScrollToEnd({animated: true});

Si vous utilisez, 0.59.8cela fonctionnera OU si vous utilisez une version ultérieure et cela fonctionne pour vous. veuillez ajouter des versions dans cette réponse afin que les autres n'aient pas de problèmes.

Code complet ici

<ScrollView
    ref={ref => this.scrollView = ref}
    onContentSizeChange={(contentWidth, contentHeight)=>{        
        this.scrollView.scrollResponderScrollToEnd({animated: true});
    }}>
</ScrollView>

1

Cette méthode est un peu piratée mais je n'ai pas trouvé de meilleur moyen

  1. Ajoutez d'abord une référence à votre ScrollView.
  2. Ajoutez ensuite une vue factice avant de fermer votre balise ScrollView
  3. Obtenez la position y de cette vue factice
  4. Chaque fois que vous voulez faire défiler vers le bas, faites défiler jusqu'à la position de la vue factice

var footerY; //Y position of the dummy view
render() {    
    return (
      <View>
          <ScrollView 
          ref='_scrollView'>
            // This is where all the things that you want to display in the scroll view goes
            
            // Below is the dummy View
            <View onLayout={(e)=> {
              footerY = e.nativeEvent.layout.y;
              }}/>
          </ScrollView>
              
          //Below is the button to scroll to the bottom    
          <TouchableHighlight
            onPress={() => { this.refs._scrollView.scrollTo(footerY); }}>
            <Text>Scroll to Bottom</Text>
          </TouchableHighlight>
        
      </View>
    );
  }


Ce n'est pas tout à fait ce qui a été demandé; cela défile vers le bas au toucher, plutôt que de garder la vue de défilement vers le bas à mesure que le contenu est ajouté. Quoi qu'il en soit, scrollToBottomrend des approches comme celle-ci obsolètes dans les nouvelles versions de React Native.
Mark Amery

1

Cela répond à votre question: https://stackoverflow.com/a/39563100/1917250

Vous devez continuer à faire défiler constamment vers le bas de la page en calculant la hauteur du contenu moins la hauteur de son scrollView.


C'est la bonne réponse à la question. Vous pouvez être intelligent pour savoir quand effectuer le défilement, la réponse dans le lien vous montre comment obtenir les chiffres dont vous avez besoin. En particulier, si vous ajoutez des composants à la liste, la hauteur du contenu ne les inclura pas lors de l'appel de componentDidUpdate, vous devez donc vous rappeler que vous voulez faire défiler et faire défiler sur onContentSizeChange à la place.
xixixao

1

Si vous utilisez TypeScript, vous devez le faire

interface Props extends TextInputProps { 
     scrollRef?: any;
}

export class Input extends React.Component<Props, any> {
     scrollRef;
constructor(props) {
    this.scrollRef = React.createRef();
}
<ScrollView
    ref={ref => (this.scrollRef = ref)}
    onContentSizeChange={() => {
    this.scrollRef.scrollToEnd();
    }}
>
</ScrollView>

0

Je me suis battu avec ça pendant un moment et j'ai finalement trouvé quelque chose qui fonctionnait vraiment bien et qui fonctionnait bien pour moi. Comme beaucoup, j'ai essayé .scrollToEnden vain. La solution qui a fonctionné pour moi était une combinaison de onContentSizeChange, onLayoutaccessoires et un peu plus penser visuellement sur « ce défilement vers le bas » signifiait vraiment. La dernière partie me semble triviale maintenant, mais il m'a fallu une éternité pour comprendre.

Étant donné que le ScrollViewa une hauteur fixe, lorsque la hauteur du contenu dépasse la hauteur du conteneur, j'ai calculé le décalage en soustrayant la prevHeight(hauteur fixe du ScrollView) pour le currHeight(la hauteur du contenu). Si vous pouvez l'imaginer visuellement, en faisant défiler jusqu'à cette différence sur l'axe des y, faites glisser la hauteur du contenu sur son conteneur de ce décalage. J'ai incrémenté mon décalage et fait de la hauteur de contenu actuelle ma hauteur précédente et j'ai continué à calculer un nouveau décalage.

Voici le code. J'espère que ça aide quelqu'un.

class ChatRoomCorrespondence extends PureComponent {
  currHeight = 0;
  prevHeight = 0;
  scrollHeight = 0;

  scrollToBottom = () => {
    this.refs.scrollView.getScrollResponder().scrollResponderScrollTo({
      x: 0,
      y: this.scrollHeight,
      animated: true
    });
  };

  render() {
    return (
      <ScrollView
        style={Styles.container}
        ref="scrollView"
        onContentSizeChange={(w, h) => {
          this.currHeight = h;

          if (
            this.prevHeight > 0 &&
            this.currHeight - this.scrollHeight > this.prevHeight
          ) {
            this.scrollHeight += this.currHeight - this.prevHeight;
            console.log("--------------------------------------------");
            console.log("Curr: ", this.currHeight);
            console.log("Prev: ", this.prevHeight);
            console.log("Scroll: ", this.scrollHeight);
            this.prevHeight = this.currHeight;
            console.log("PREV: ", this.prevHeight);
            console.log("--------------------------------------------");

            this.scrollToBottom();
          }
        }}
        onLayout={ev => {
          // Fires once
          const fixedContentHeight = ev.nativeEvent.layout.height;
          this.prevHeight = fixedContentHeight;
        }}
      >
        <FlatList
          data={this.props.messages}
          renderItem={({ item }) => (
            <ChatMessage
              text={item.text}
              sender={item.sender}
              time={item.time}
              username={this.props.username}
            />
          )}
          keyExtractor={(item, index) => index.toString()}
        />
      </ScrollView>
    );
  }
}

0

Je suppose qu'il est trop tard pour répondre mais j'ai eu un hack! Si vous utilisez , FlatListvous pouvez activer la invertedpropriété qui revient à la mise transform scaleà -1( ce qui est acceptable pour d' autres composants de la liste comme ScrollView...). Lorsque vous effectuez le rendu FlatListavec des données, il sera toujours en bas!


-1

Essayez de définir la propriété contentOffset de la vue de défilement sur la hauteur actuelle du contenu.

Pour accomplir ce dont vous avez besoin, vous pouvez le faire dans le cadre de l'événement onScroll. Cependant, cela peut entraîner une mauvaise expérience utilisateur, il peut donc s'avérer plus convivial de ne le faire que lorsqu'un nouveau message est ajouté.


-1; Nan. Comme beaucoup de réponses ici, cela a pour effet de faire défiler ScrollViewvers le bas tout son contenu, plutôt que de simplement faire défiler vers le bas.
Mark Amery


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.