È possibile mantenere uno ScrollView fatto scorrere fino in fondo?


90

Per un'app simile a una chat, voglio che un ScrollViewcomponente scorra fino in fondo, perché i messaggi più recenti vengono visualizzati sotto quelli più vecchi. Possiamo regolare la posizione di scorrimento di a ScrollView?


3
Non so nulla di React-Native, ma tieni presente che a volte gli utenti desiderano scorrere verso l'alto per visualizzare la cronologia chat. Assicurati di scorrere la chat solo se la chat era già stata fatta scorrere fino in fondo quando arriva un nuovo messaggio.
David

Questa è una buona idea. Stiamo monitorando questo problema qui: github.com/facebook/react-native/issues/107
Eric Vicenti

Ho appena risposto a una domanda simile, si prega di controllare qui - stackoverflow.com/a/32652967/1541508
Kohver

Risposte:


166

Per React Native 0.41 e versioni successive , puoi farlo con il scrollToEndmetodo integrato :

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

3
Da React Native 0.55.4 e versioni successive, ho dovuto usare rootaltrimenti scrollToEnd non è definito. vale a direthis.scrollView.root.scrollToEnd
Simon

4
Attualmente l'utilizzo di React Native 0.58.6 e l'utilizzo in rootrealtà genera un errore indefinito.
Jose Bernhardt

1
Dovrebbe essere: ref={ref => {this.scrollView = ref}}poiché non desideri restituire il compito
Hampycalc

26

Utilizzare onContentSizeChange per tenere traccia della Y inferiore della visualizzazione a scorrimento (altezza)

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

var _scrollToBottomY

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

quindi utilizzare il nome di riferimento della visualizzazione a scorrimento come

this.refs.scrollView.scrollTo(_scrollToBottomY);

2
Nota che questo scorre la vista completamente verso il basso, quindi la vista non è essenzialmente visibile (a meno che tu non abbia aggiunto qualcosa dopo la misurazione). Questo ha senso dal momento che il codice scorre all'altezza dell'area scorrevole, non all'altezza dei bambini che traboccano l'area scorrevole (probabilmente NON quello che voleva OP). Vedi invece stackoverflow.com/a/39563193/1526986 .
xixixao

21

Ecco la soluzione più semplice che potrei trovare:

 <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
Quando uso questa o altre risposte simili, gli elementi nell'elenco scompaiono (sembra che scorra troppo , ma non posso esserne sicuro). Scorrendo un po 'tutto fa riapparire e sembra che stia tornando alla vista (da qui la teoria dello scorrimento troppo lontano). Qualche idea ovvia del perché potrebbe essere?
tscizzle

16

Per chiunque scriva un componente funzione che può utilizzare 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 })}
    />
  );
};

1
semplice ed elegante. fa perfettamente quello che volevo! grazie!
Qamar Stationwala

6

Nel caso in cui qualcuno nel 2020 o successivamente si chieda come ottenere questo risultato con componenti funzionali. Ecco come ho fatto:

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

Nota: puoi menzionare che stai usando useref. la maggior parte delle persone non sa come usare ref con gli hook.
ʎzɐɹƆ

sì. Il fatto è che ha funzionato anche quando non si usa l'hook useRef. Ma il modo giusto è usarlo! Hai ragione
Marlon Englemam

5

prova questa soluzione

xcode 10.0

RN: 56.0.0

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

perchè ref=? sembra una reliquia dello
sviluppo

4

È possibile invertire la visualizzazione a scorrimento / elenco utilizzando il react-native-invertible-scroll-viewmodulo , quindi chiamare semplicementeref.scrollTo(0) per scorrere fino in fondo.

Installa il modulo:

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

Quindi importa il componente:

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

Quindi utilizza il seguente JSX invece di ScrollView:

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

Funziona perché react-native-invertible-scroll-viewcapovolge il contenuto dell'intera visualizzazione. Nota che anche i figli della vista verranno renderizzati nell'ordine opposto a una vista normale: il primo figlio apparirà in basso .


3

In realtà la risposta di @Aniruddha non ha funzionato per me perché sto usando "react-native": "0.59.8"ed this.scrollView.root.scrollToEndè anche indefinita

ma l'ho capito e funziona this.scrollView.scrollResponderScrollToEnd({animated: true});

Se stai usando 0.59.8questo funzionerà OPPURE se stai usando una versione successiva e questo funziona per te. si prega di aggiungere versioni in questa risposta in modo che gli altri non abbiano problemi.

Codice completo qui

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

1

Questo metodo è un po 'complicato ma non sono riuscito a trovare un modo migliore

  1. Per prima cosa aggiungi un riferimento al tuo ScrollView.
  2. In secondo luogo, aggiungi una vista fittizia prima di chiudere il tag ScrollView
  3. Ottieni la posizione y di quella vista fittizia
  4. Ogni volta che si desidera scorrere fino in fondo, scorrere fino alla posizione della vista fittizia

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


Questo non è esattamente ciò che è stato chiesto; questo scorre fino in fondo al tocco, invece di mantenere la visualizzazione a scorrimento scorrevole verso il basso man mano che il contenuto viene aggiunto. Ad ogni modo, scrollToBottomrende approcci come questo obsoleti nelle versioni più recenti di React Native.
Mark Amery

1

Questo risponde alla tua domanda: https://stackoverflow.com/a/39563100/1917250

È necessario continuare a scorrere costantemente fino alla fine della pagina calcolando l'altezza del contenuto meno l'altezza del suo scrollView.


Questa è la risposta corretta alla domanda. Puoi essere intelligente su quando eseguire lo scorrimento, la risposta nel link ti mostra come ottenere i numeri di cui hai bisogno. In particolare, se si aggiungono componenti all'elenco, l'altezza del contenuto non li includerà quando viene chiamato componentDidUpdate, quindi è opportuno ricordare che si desidera scorrere e scorrere invece su ContentSizeChange.
xixixao

1

Se usi TypeScript, devi farlo

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

Ho combattuto con questo per un po 'e alla fine ho trovato qualcosa che funzionava davvero bene e senza intoppi per me. Come molti, ho provato .scrollToEndinutilmente. La soluzione che ha funzionato per me è stata una combinazione di onContentSizeChange,onLayout oggetti di scena e un po 'di più a pensare visivamente su "ciò che scorrendo verso il basso" in realtà significava. L'ultima parte ora mi sembra banale, ma mi ci è voluta un'eternità per capirla.

Dato che ScrollViewha un'altezza fissa, quando l'altezza del contenuto supera l'altezza del contenitore, ho calcolato l'offset sottraendo l ' prevHeight(altezza fissa del ScrollView) per ilcurrHeight (l'altezza del contenuto). Se puoi immaginarlo visivamente, scorrendo fino a quella differenza nell'asse y, fai scorrere l'altezza del contenuto sul suo contenitore di tale offset. Ho incrementato il mio offset e ho impostato l'altezza del contenuto attuale come quella precedente e ho continuato a calcolare un nuovo offset.

Ecco il codice. Spero che aiuti qualcuno.

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

Immagino sia troppo tardi per rispondere ma ho un trucco! Se si utilizza FlatListè possibile attivare la invertedproprietà che ricade impostazione transform scalea -1(che è accettabile per gli altri componenti della lista, come ScrollView...). Quando esegui il rendering FlatListcon i dati, sarà sempre in fondo!


-1

Prova a impostare la proprietà contentOffset della visualizzazione a scorrimento sull'altezza corrente del contenuto.

Per realizzare ciò di cui hai bisogno potresti farlo come parte dell'evento onScroll. Tuttavia, ciò potrebbe creare una cattiva esperienza utente, quindi potrebbe rivelarsi più facile da usare farlo solo quando viene aggiunto un nuovo messaggio.


-1; no. Come molte delle risposte qui, questo ha l'effetto di scorrere ScrollViewverso il basso fino al di sotto di tutto il suo contenuto, piuttosto che scorrere verso il basso.
Mark Amery


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.