Ruft die aktuelle Bildlaufposition von ScrollView in React Native ab


115

Ist es möglich, die aktuelle Bildlaufposition oder die aktuelle Seite einer <ScrollView>Komponente in React Native abzurufen?

Also so etwas wie:

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

Relevant: Ein GitHub-Problem, das vorschlägt, eine bequeme Möglichkeit zum Abrufen dieser Informationen hinzuzufügen.
Mark Amery

Antworten:


207

Versuchen.

<ScrollView onScroll={this.handleScroll} />

Und dann:

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

5
Dies wird nicht immer ausgelöst, wenn sich der Bildlaufwert ändert. Wenn beispielsweise die Größe der Bildlaufansicht geändert wird und nun das Ganze auf einmal auf dem Bildschirm angezeigt werden kann, wird nicht immer ein onScroll-Ereignis angezeigt.
Thomas Parslow

19
Oder event.nativeEvent.contentOffset.xwenn du eine horizontale ScrollView hast;) Danke!
Tieme

2
Dies können wir auch in Android verwenden.
Janaka Pushpakumara

4
Es ist frustrierend, dass scrollEventThrottlees keine Garantie dafür gibt, dass der Versatz im letzten Bild angezeigt wird, es sei denn, Sie stellen 16 oder weniger ein (um ein Ereignis für jedes einzelne Bild zu erhalten). Dies bedeutet, dass Sie nach Abschluss des Bildlaufs den richtigen Versatz haben. Sie müssen scrollEventThrottle={16}die Auswirkungen auf die Leistung festlegen und akzeptieren.
Mark Amery

@bradoyler: ist es möglich den Maximalwert von zu kennen event.nativeEvent.contentOffset.y?
Isaac

54

Haftungsausschluss: Was folgt, ist in erster Linie das Ergebnis meiner eigenen Experimente in React Native 0.50. In der ScrollViewDokumentation fehlen derzeit viele der unten behandelten Informationen. zum BeispielonScrollEndDrag ist völlig undokumentiert. Da hier alles auf undokumentiertem Verhalten beruht, kann ich leider nicht versprechen, dass diese Informationen in einem Jahr oder sogar einem Monat korrekt bleiben.

Außerdem setzt alles unten eine rein vertikale Bildlaufansicht voraus, deren y- Versatz uns interessiert; Das Übersetzen in x Offsets, falls erforderlich, ist hoffentlich eine einfache Übung für den Leser.


Verschiedene Event-Handler ScrollViewnehmen eine Aufnahme eventund lassen Sie die aktuelle Bildlaufposition über abrufen event.nativeEvent.contentOffset.y. Einige dieser Handler haben ein etwas anderes Verhalten zwischen Android und iOS, wie unten beschrieben.

onScroll

Auf Android

Löst jedes Bild aus, während der Benutzer einen Bildlauf durchführt, auf jedem Bild, während die Bildlaufansicht nach dem Loslassen durch den Benutzer gleitet, auf dem letzten Bild, wenn die Bildlaufansicht zum Stillstand kommt, und auch dann, wenn sich der Versatz der Bildlaufansicht aufgrund des Bilds ändert Ändern (z. B. durch Drehung von Querformat zu Hochformat).

Unter iOS

Wird ausgelöst, während der Benutzer zieht oder während die Bildlaufansicht gleitet, mit einer Häufigkeit, die von scrollEventThrottleund höchstens einmal pro Bild bestimmt wird, wenn scrollEventThrottle={16}. Wenn der Benutzer die Bildlaufansicht freigibt, während er genügend Schwung zum Gleiten hat, wird der onScrollHandler auch ausgelöst, wenn er nach dem Gleiten zur Ruhe kommt. Wenn der Benutzer die Bildlaufansicht jedoch zieht und dann im Stillstand freigibt, onScrollist dies nicht der Fall garantiert, dass sie für die endgültige Position ausgelöst scrollEventThrottlewird, es sei denn, sie wurde so eingestellt, dass onScrolljedes Bildlaufbild ausgelöst wird.

Das Einstellen verursacht Leistungskosten, die scrollEventThrottle={16}durch Einstellen auf eine größere Anzahl reduziert werden können. Dies bedeutet jedoch, dass onScrollnicht jeder Frame ausgelöst wird.

onMomentumScrollEnd

Wird ausgelöst, wenn die Bildlaufansicht nach dem Gleiten zum Stillstand kommt. Wird überhaupt nicht ausgelöst, wenn der Benutzer die Bildlaufansicht im Stillstand so freigibt, dass sie nicht gleitet.

onScrollEndDrag

Wird ausgelöst, wenn der Benutzer das Ziehen der Bildlaufansicht beendet - unabhängig davon, ob die Bildlaufansicht stationär bleibt oder zu gleiten beginnt.


Angesichts dieser Verhaltensunterschiede hängt der beste Weg, den Versatz zu verfolgen, von Ihren genauen Umständen ab. Im kompliziertesten Fall (Sie müssen Android und iOS unterstützen, einschließlich der Behandlung von Änderungen im ScrollViewRahmen aufgrund von Rotation, und Sie möchten die Leistungseinbußen bei Android von Einstellung scrollEventThrottleauf 16 nicht akzeptieren ), und Sie müssen damit umgehen ändert sich auch der Inhalt in der Bildlaufansicht, dann ist es ein verdammtes Durcheinander.

Der einfachste Fall ist, wenn Sie nur mit Android umgehen müssen. benutze einfach onScroll:

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

Um iOS zusätzlich zu unterstützen , ist es nur ein bisschen komplizierter , wenn Sie den onScrollHandler gerne in jedem Frame auslösen und die Auswirkungen auf die Leistung akzeptieren und wenn Sie keine Frame-Änderungen vornehmen müssen:

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

Um den Leistungsaufwand unter iOS zu verringern und gleichzeitig sicherzustellen, dass alle Positionen aufgezeichnet werden, an denen sich die Bildlaufansicht befindet, können wir scrollEventThrottleden onScrollEndDragHandler erhöhen und zusätzlich bereitstellen :

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

Wenn wir jedoch Rahmenänderungen (z. B. weil wir das Gerät drehen lassen, die verfügbare Höhe für den Rahmen der Bildlaufansicht ändern) und / oder Inhaltsänderungen verarbeiten möchten, müssen wir beide zusätzlich implementieren onContentSizeChangeund onLayoutdie Höhe beider verfolgen den Rahmen und den Inhalt der Bildlaufansicht und berechnen dabei kontinuierlich das Maximum möglichen Versatz und schließen daraus, wenn der Versatz aufgrund einer Änderung der Rahmen- oder Inhaltsgröße automatisch verringert wurde:

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

Ja, es ist ziemlich schrecklich. Ich bin mir auch nicht 100% sicher, dass es immer richtig funktioniert, wenn Sie gleichzeitig die Größe des Rahmens und den Inhalt der Bildlaufansicht ändern. Aber es ist das Beste, was ich mir vorstellen kann, und bis diese Funktion innerhalb des Frameworks selbst hinzugefügt wird , denke ich, dass dies das Beste ist, was jeder tun kann.


19

Brad Oylers Antwort ist richtig. Sie erhalten jedoch nur eine Veranstaltung. Wenn Sie die Bildlaufposition ständig aktualisieren müssen, sollten Sie die scrollEventThrottleRequisite wie folgt einstellen :

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

Und der Event-Handler:

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

Beachten Sie, dass möglicherweise Leistungsprobleme auftreten. Stellen Sie den Gashebel entsprechend ein. 16 gibt Ihnen die meisten Updates. 0 nur eine.


12
Das ist falsch. Zum einen funktioniert scrollEvenThrottle nur für iOS und nicht für Android. Zweitens wird nur geregelt, wie oft Sie onScroll-Anrufe erhalten. Die Standardeinstellung ist einmal pro Frame, nicht ein Ereignis. 1 gibt Ihnen mehr Updates und 16 gibt Ihnen weniger. 0 ist Standard. Diese Antwort ist völlig falsch. facebook.github.io/react-native/docs/…
Teodors

1
Ich habe darauf geantwortet, bevor Android von React Native unterstützt wurde. Ja, die Antwort gilt nur für iOS. Der Standardwert ist Null. Dies führt dazu, dass das Bildlaufereignis bei jedem Bildlauf der Ansicht nur einmal gesendet wird.
Cutemachine

1
Was wäre die es6-Notation für function (event: Object) {}?
Cbartondock

1
Einfach function(event) { }.
Cutemachine

1
@Teodors Während diese Antwort Android nicht abdeckt, ist der Rest Ihrer Kritik völlig verwirrt. scrollEventThrottle={16}ist nicht geben Sie „weniger“ Updates; Jede Zahl im Bereich von 1 bis 16 gibt Ihnen die maximal mögliche Aktualisierungsrate. Der Standardwert 0 gibt Ihnen (unter iOS) ein Ereignis, wenn der Benutzer mit dem Scrollen beginnt, und dann keine nachfolgenden Updates (was ziemlich nutzlos und möglicherweise ein Fehler ist). Der Standardwert ist nicht "einmal pro Frame". Abgesehen von diesen beiden Fehlern in Ihrem Kommentar widersprechen Ihre anderen Behauptungen nichts, was in der Antwort steht (obwohl Sie zu glauben scheinen, dass dies der Fall ist).
Mark Amery

7

Wenn Sie einfach die aktuelle Position abrufen möchten (z. B. wenn eine Taste gedrückt wird), anstatt sie zu verfolgen, wenn der Benutzer einen Bildlauf durchführt, führt das Aufrufen eines onScrollListeners zu Leistungsproblemen. Derzeit ist die reaktionsschnellste Methode, um einfach die aktuelle Bildlaufposition abzurufen, die Verwendung von react-native-invoke Pakets . Es gibt ein Beispiel für genau diese Sache, aber das Paket macht mehrere andere Dinge.

Lesen Sie hier darüber. https://medium.com/@talkol/invoke-any-native-api-directly-from-pure-javascript-in-react-native-1fb6afcdf57d#.68ls1sopd


2
Wissen Sie, ob es jetzt eine sauberere (etwas weniger hackige) Möglichkeit gibt, Offset anzufordern, oder ist dies immer noch die beste Methode, die Sie kennen? (Ich frage mich, warum diese Antwort nicht positiv bewertet wird, da sie die einzige ist, die Sinn
ergibt

1
Das Paket ist nicht mehr da, ist es irgendwohin gezogen? Github zeigt 404.
Noitidart

6

Der einfachste Weg, um das x / y nach dem Ende des Bildlaufs zu erhalten, als die ursprünglichen Fragen angefordert wurden, ist wahrscheinlich der folgende:

<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; onMomentumScrollEndwird nur ausgelöst, wenn die Bildlaufansicht einen gewissen Schwung hat, wenn der Benutzer sie loslässt. Wenn sie freigegeben werden, während sich die Bildlaufansicht nicht bewegt, erhalten Sie nie eines der Momentum-Ereignisse.
Mark Amery

1
Ich habe onMomentumScrollEnd getestet und es war mir unmöglich, es nicht auszulösen. Ich habe versucht, auf verschiedene Arten zu scrollen. Ich habe versucht, die Berührung freizugeben, wenn sich der Bildlauf in der Endposition befand, und es wurde auch ausgelöst. Diese Antwort ist also richtig, soweit ich sie testen konnte.
Fermmm

6

Die oben genannten Antworten sagen , wie die Position zu bekommen verschiedene API, onScroll, onMomentumScrollEndetc; Wenn Sie den Seitenindex kennen möchten, können Sie ihn anhand des Versatzwerts berechnen.

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

Geben Sie hier die Bildbeschreibung ein

In iOS ist die Beziehung zwischen ScrollView und dem sichtbaren Bereich wie folgt: Das Bild stammt von https://www.objc.io/issues/3-views/scroll-view/

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


Wenn Sie den "Index" des aktuellen Elements erhalten möchten, ist dies die richtige Antwort. Sie nehmen event.nativeEvent.contentOffset.x / deviceWidth. Danke für Ihre Hilfe!
Sara Inés Calderón

1

Auf diese Weise wurde die aktuelle Bildlaufposition live aus der Ansicht angezeigt. Ich verwende lediglich den On-Scroll-Ereignis-Listener und das scrollEventThrottle. Ich übergebe es dann als Ereignis, um die von mir erstellte Bildlauffunktion zu handhaben und meine Requisiten zu aktualisieren.

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

Ist es gut, den Status von onScroll unter Berücksichtigung der Leistung zu aktualisieren?
Hrishi

1

Die Verwendung von onScroll tritt in eine Endlosschleife ein. Stattdessen können onMomentumScrollEnd oder onScrollEndDrag verwendet werden


0

Was die Seite betrifft, arbeite ich an einer Komponente höherer Ordnung, die im Grunde die oben genannten Methoden verwendet, um genau dies zu tun. Tatsächlich dauert es nur ein wenig, bis Sie sich mit den Feinheiten wie dem anfänglichen Layout und den Änderungen des Inhalts befassen. Ich werde nicht behaupten, es "richtig" gemacht zu haben, aber in gewissem Sinne würde ich die richtige Antwort in Betracht ziehen, um eine Komponente zu verwenden, die dies sorgfältig und konsequent tut.

Siehe: React-Native-Paged-Scroll-Ansicht . Würde mich über Feedback freuen, auch wenn ich alles falsch gemacht habe!


1
das ist fantastisch. Danke, dass du das gemacht hast. Ich fand das sofort sehr nützlich.
Marty Cortez

So froh! Schätzen Sie wirklich das Feedback!

1
Eine Entschuldigung -1, da das Projekt offen zugibt, dass es nicht gewartet wird und dass die Komponente "ein paar hässliche Fehler" aufweist .
Mark Amery

Vielen Dank für das Feedback @MarkAmery. :) Die Zeit für Open Source zu finden ist nicht einfach.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.