Ottieni la posizione di scorrimento corrente di ScrollView in React Native


115

È possibile ottenere la posizione di scorrimento corrente o la pagina corrente di un <ScrollView>componente in React Native?

Quindi qualcosa come:

<ScrollView
  horizontal={true}
  pagingEnabled={true}
  onScrollAnimationEnd={() => { 
      // get this scrollview's current page or x/y scroll position
  }}>
  this.state.data.map(function(e, i) { 
      <ImageCt key={i}></ImageCt> 
  })
</ScrollView>

Rilevante: un problema di GitHub che suggerisce di aggiungere un modo conveniente per ottenere queste informazioni.
Mark Amery

Risposte:


207

Provare.

<ScrollView onScroll={this.handleScroll} />

E poi:

handleScroll: function(event: Object) {
 console.log(event.nativeEvent.contentOffset.y);
},

5
Questo non si attiverà sempre quando il valore di scorrimento cambia. Se ad esempio scrollview viene ridimensionato in modo che ora sia in grado di visualizzare l'intera cosa sullo schermo contemporaneamente, non sempre sembra che venga visualizzato un evento onScroll.
Thomas Parslow

19
O event.nativeEvent.contentOffset.xse hai una ScrollView orizzontale;) Grazie!
Tieme

2
Anche questo lo possiamo usare su Android.
Janaka Pushpakumara

4
In modo frustrante, a meno che non si imposti scrollEventThrottlea 16 o inferiore (per ottenere un evento per ogni singolo frame), non c'è alcuna garanzia che questo riporterà l'offset sul frame finale , il che significa che per essere sicuri di avere l'offset giusto dopo che lo scorrimento è terminato, è necessario impostare scrollEventThrottle={16}e accettare l'impatto di ciò sulle prestazioni.
Mark Amery

@bradoyler: è possibile conoscere il valore massimo di event.nativeEvent.contentOffset.y?
Isaac

55

Disclaimer: ciò che segue è principalmente il risultato della mia sperimentazione in React Native 0.50. Nella ScrollViewdocumentazione mancano attualmente molte delle informazioni trattate di seguito; per esempio onScrollEndDragè completamente priva di documenti. Poiché qui tutto si basa su comportamenti non documentati, purtroppo non posso promettere che queste informazioni rimarranno corrette per un anno o anche un mese da oggi.

Inoltre, tutto ciò che segue presuppone una visualizzazione a scorrimento puramente verticale di cui siamo interessati all'offset y ; si spera che tradurre in x offset, se necessario, sia un esercizio facile per il lettore.


Vari gestori di eventi su una ScrollViewripresa evente consentono di ottenere la posizione di scorrimento corrente tramite event.nativeEvent.contentOffset.y. Alcuni di questi gestori hanno un comportamento leggermente diverso tra Android e iOS, come descritto di seguito.

onScroll

Su Android

Spara ogni fotogramma mentre l'utente sta scorrendo, su ogni fotogramma mentre la vista a scorrimento scorre dopo che l'utente l'ha rilasciata, sul fotogramma finale quando la vista a scorrimento si ferma, e anche ogni volta che l'offset della vista a scorrimento cambia a causa del suo fotogramma cambiando (ad esempio a causa della rotazione da orizzontale a verticale).

Su iOS

Si attiva mentre l'utente sta trascinando o mentre la visualizzazione di scorrimento scorre, a una frequenza determinata da scrollEventThrottlee al massimo una volta per fotogramma quando scrollEventThrottle={16}. Se l'utente rilascia la visualizzazione a scorrimento mentre ha abbastanza slancio per planare, il onScrollconduttore scatterà anche quando si ferma dopo la planata. Tuttavia, se l'utente trascina e quindi rilascia la visualizzazione a scorrimento mentre è ferma, nononScroll è garantito che si attivi per la posizione finale a meno che non sia stato impostato in modo tale da attivare ogni fotogramma di scorrimento.scrollEventThrottleonScroll

L'impostazione ha un costo in termini di prestazioni scrollEventThrottle={16}che può essere ridotto impostandolo su un numero maggiore. Tuttavia, ciò significa che onScrollnon verrà attivato ogni fotogramma.

onMomentumScrollEnd

Si attiva quando la visualizzazione a scorrimento si ferma dopo lo scorrimento. Non si attiva affatto se l'utente rilascia la visualizzazione a scorrimento mentre è ferma in modo che non scivoli.

onScrollEndDrag

Si attiva quando l'utente smette di trascinare la visualizzazione a scorrimento, indipendentemente dal fatto che la visualizzazione a scorrimento sia lasciata ferma o inizi a scorrere.


Date queste differenze di comportamento, il modo migliore per tenere traccia dell'offset dipende dalle circostanze specifiche. Nel caso più complicato (è necessario supportare Android e iOS, inclusa la gestione delle modifiche nel ScrollViewframe di a causa della rotazione, e non si desidera accettare la penalizzazione delle prestazioni su Android dall'impostazione scrollEventThrottlea 16), e è necessario gestire cambia anche il contenuto nella visualizzazione a scorrimento, quindi è un vero casino.

Il caso più semplice è se devi solo gestire Android; basta usare onScroll:

<ScrollView
  onScroll={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y
  }}
>

Per supportare ulteriormente iOS, se sei felice di attivare il onScrollgestore ogni frame e accettare le implicazioni sulle prestazioni di questo, e se non hai bisogno di gestire i cambiamenti di frame, allora è solo un po 'più complicato:

<ScrollView
  onScroll={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y
  }}
  scrollEventThrottle={16}
>

Per ridurre il sovraccarico delle prestazioni su iOS pur continuando a garantire che registriamo qualsiasi posizione su cui si stabilisce la visualizzazione a scorrimento, possiamo aumentare scrollEventThrottlee fornire in aggiunta un onScrollEndDraggestore:

<ScrollView
  onScroll={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y
  }}
  onScrollEndDrag={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y
  }}
  scrollEventThrottle={160}
>

Ma se vogliamo gestire le modifiche al frame (ad es. Perché permettiamo al dispositivo di essere ruotato, cambiando l'altezza disponibile per il frame della visualizzazione a scorrimento) e / o le modifiche al contenuto, allora dobbiamo implementarli entrambi onContentSizeChangee onLayouttenere traccia dell'altezza di entrambi il frame della visualizzazione a scorrimento e il suo contenuto, quindi calcolare continuamente l' offset massimo possibile e dedurre quando l'offset è stato ridotto automaticamente a causa di una modifica della dimensione del frame o del contenuto:

<ScrollView
  onLayout={event => {
    this.frameHeight = event.nativeEvent.layout.height;
    const maxOffset = this.contentHeight - this.frameHeight;
    if (maxOffset < this.yOffset) {
      this.yOffset = maxOffset;
    }
  }}
  onContentSizeChange={(contentWidth, contentHeight) => {
    this.contentHeight = contentHeight;
    const maxOffset = this.contentHeight - this.frameHeight;
    if (maxOffset < this.yOffset) {
      this.yOffset = maxOffset;
    }
  }}
  onScroll={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y;
  }}
  onScrollEndDrag={event => { 
    this.yOffset = event.nativeEvent.contentOffset.y;
  }}
  scrollEventThrottle={160}
>

Sì, è piuttosto orribile. Inoltre, non sono sicuro al 100% che funzionerà sempre correttamente nei casi in cui modifichi simultaneamente la dimensione del frame e del contenuto della visualizzazione a scorrimento. Ma è il meglio che riesco a trovare e fino a quando questa funzione non viene aggiunta all'interno del framework stesso , penso che questo sia il meglio che chiunque possa fare.


19

La risposta di Brad Oyler è corretta. Ma riceverai solo un evento. Se hai bisogno di ottenere aggiornamenti costanti della posizione di scorrimento, dovresti impostare l' scrollEventThrottleelica, in questo modo:

<ScrollView onScroll={this.handleScroll} scrollEventThrottle={16} >
  <Text>
    Be like water my friend 
  </Text>
</ScrollView>

E il gestore di eventi:

handleScroll: function(event: Object) {
  console.log(event.nativeEvent.contentOffset.y);
},

Tieni presente che potresti incorrere in problemi di prestazioni. Regolare di conseguenza l'acceleratore. 16 ti offre il maggior numero di aggiornamenti. 0 solo uno.


12
Questo non è corretto. Per uno, scrollEvenThrottle funziona solo per iOS e non per Android. In secondo luogo, regola solo la frequenza con cui ricevi chiamate onScroll. L'impostazione predefinita è una volta per frame, non un evento. 1 ti dà più aggiornamenti e 16 ti dà meno. 0 è l'impostazione predefinita. Questa risposta è completamente sbagliata. facebook.github.io/react-native/docs/…
Teodors

1
Ho risposto prima che Android fosse supportato da React Native, quindi sì, la risposta si applica solo a iOS. Il valore predefinito è zero, che fa sì che l'evento di scorrimento venga inviato solo una volta ogni volta che si scorre la vista.
cutemachine

1
Quale sarebbe la notazione es6 per function (event: Object) {}?
cbartondock

1
Solo function(event) { }.
cutemachine

1
@Teodors Anche se questa risposta non copre Android, il resto delle tue critiche è completamente confuso. scrollEventThrottle={16}non ti dà "meno" aggiornamenti; qualsiasi numero compreso tra 1 e 16 offre la massima frequenza possibile di aggiornamenti. L'impostazione predefinita di 0 ti dà (su iOS) un evento quando l'utente inizia a scorrere e quindi nessun aggiornamento successivo (che è abbastanza inutile e forse un bug); l'impostazione predefinita non è "una volta per frame". Oltre a questi due errori nel tuo commento, le tue altre affermazioni non contraddicono nulla di ciò che dice la risposta (anche se sembri pensare che lo facciano).
Mark Amery

7

Se desideri semplicemente ottenere la posizione corrente (ad esempio quando viene premuto un pulsante) invece di seguirla ogni volta che l'utente scorre, allora invocare un onScrollascoltatore causerà problemi di prestazioni. Attualmente il modo più performante per ottenere semplicemente la posizione di scorrimento corrente è usare react-native-invoke pacchetto . C'è un esempio per questa cosa esatta, ma il pacchetto fa molte altre cose.

Leggi qui. https://medium.com/@talkol/invoke-any-native-api-directly-from-pure-javascript-in-react-native-1fb6afcdf57d#.68ls1sopd


2
Sai se esiste un modo più pulito (un po 'meno hacky) per richiedere l'offset ora, o è ancora il modo migliore di farlo di cui sei a conoscenza? (Mi chiedo perché questa risposta non sia votata positivamente in quanto è l'unica che sembra avere un senso)
cglacet

1
Il pacco non c'è più, si è spostato da qualche parte? Github mostra 404.
Noitidart

6

Per ottenere la x / y dopo che lo scorrimento è terminato come richiesto dalle domande originali, il modo più semplice è probabilmente questo:

<ScrollView
   horizontal={true}
   pagingEnabled={true}
   onMomentumScrollEnd={(event) => { 
      // scroll animation ended
      console.log(e.nativeEvent.contentOffset.x);
      console.log(e.nativeEvent.contentOffset.y);
   }}>
   ...content
</ScrollView>

-1; onMomentumScrollEndviene attivato solo se la visualizzazione a scorrimento ha un certo slancio quando l'utente la lascia andare. Se si rilasciano mentre la visualizzazione a scorrimento non è in movimento, non otterrai mai nessuno degli eventi di slancio.
Mark Amery

1
Ho provato suMomentumScrollEnd ed era impossibile per me non attivarlo, ho provato a scorrere in modi diversi, ho provato a rilasciare il tocco quando lo scroll era nella posizione finale ed è stato anche attivato. Quindi questa risposta è corretta per quanto ho potuto testarla.
fermmm

6

Le risposte sopra spiegano come ottenere la posizione utilizzando API diverse onScroll, onMomentumScrollEndecc. Se vuoi conoscere l'indice della pagina, puoi calcolarlo utilizzando il valore di offset.

 <ScrollView 
    pagingEnabled={true}
    onMomentumScrollEnd={this._onMomentumScrollEnd}>
    {pages}
 </ScrollView> 

  _onMomentumScrollEnd = ({ nativeEvent }: any) => {
   // the current offset, {x: number, y: number} 
   const position = nativeEvent.contentOffset; 
   // page index 
   const index = Math.round(nativeEvent.contentOffset.x / PAGE_WIDTH);

   if (index !== this.state.currentIndex) {
     // onPageDidChanged
   }
 };

inserisci qui la descrizione dell'immagine

In iOS, la relazione tra ScrollView e la regione visibile è la seguente: L'immagine proviene da https://www.objc.io/issues/3-views/scroll-view/

rif: https://www.objc.io/issues/3-views/scroll-view/


Se vuoi ottenere l '"indice" dell'elemento corrente, questa è la risposta corretta. Prendi event.nativeEvent.contentOffset.x / deviceWidth. Grazie per l'aiuto!
Sara Inés Calderón

1

Ecco come è finita per ottenere la posizione di scorrimento corrente dal vivo dalla vista. Tutto quello che sto facendo è utilizzare il listener di eventi di scorrimento e scrollEventThrottle. Lo passo quindi come evento per gestire la funzione di scorrimento che ho creato e aggiornare i miei oggetti di scena.

 export default class Anim extends React.Component {
          constructor(props) {
             super(props);
             this.state = {
               yPos: 0,
             };
           }

        handleScroll(event){
            this.setState({
              yPos : event.nativeEvent.contentOffset.y,
            })
        }

        render() {
            return (
          <ScrollView onScroll={this.handleScroll.bind(this)} scrollEventThrottle={16} />
    )
    }
    }

è bene aggiornare lo stato su onScroll considerando le prestazioni?
Hrishi,

1

l'uso di onScroll entra in un ciclo infinito. Al suo posto è possibile utilizzare onMomentumScrollEnd o onScrollEndDrag


0

Per quanto riguarda la pagina, sto lavorando su un componente di ordine superiore che utilizza fondamentalmente i metodi sopra per fare esattamente questo. In realtà ci vuole solo un po 'di tempo quando si arriva a sottigliezze come il layout iniziale e le modifiche ai contenuti. Non pretendo di averlo fatto "correttamente", ma in un certo senso considererei la risposta corretta per utilizzare un componente che lo faccia con attenzione e coerenza.

Vedi: react-native-paged-scroll-view . Mi piacerebbe un feedback, anche se è che ho sbagliato tutto!


1
Questo e spettacolare. Grazie di averlo fatto. l'ho trovato molto utile subito.
Marty Cortez

Così contento! Apprezzo davvero il feedback!

1
Un apologetico -1, dal momento che il progetto ammette apertamente di non essere mantenuto e che il componente ha "un paio di brutti bug" .
Mark Amery

Grazie per il feedback @MarkAmery. :) Trovare il tempo per l'open source non è facile.

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.