Uzyskaj aktualną pozycję przewijania ScrollView w React Native


115

Czy jest możliwe uzyskanie aktualnej pozycji przewijania lub bieżącej strony <ScrollView>komponentu w React Native?

Więc coś takiego:

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

Istotne: problem z GitHub sugerujący dodanie wygodnego sposobu uzyskiwania tych informacji.
Mark Amery,

Odpowiedzi:


208

Próbować.

<ScrollView onScroll={this.handleScroll} />

I wtedy:

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

5
To nie zawsze uruchamia się, gdy zmienia się wartość przewijania. Jeśli na przykład scrollview ma zmieniony rozmiar, co powoduje, że może teraz wyświetlić całość na ekranie od razu, nie zawsze wydaje się, że zdarzenie onScroll ci mówi.
Thomas Parslow,

19
Lub event.nativeEvent.contentOffset.xjeśli masz poziomy ScrollView;) Dzięki!
Tieme,

2
Tego też możemy używać w Androidzie.
Janaka Pushpakumara

4
Frustrujące jest to, że jeśli nie ustawisz wartości scrollEventThrottle16 lub niższej (aby uzyskać zdarzenie dla każdej klatki), nie ma gwarancji, że zgłosi to przesunięcie w ostatniej klatce - co oznacza, że ​​aby mieć pewność, że masz właściwe przesunięcie po zakończeniu przewijania, musisz ustawić scrollEventThrottle={16}i zaakceptować wpływ tego na wydajność.
Mark Amery,

@bradoyler: czy można poznać maksymalną wartość event.nativeEvent.contentOffset.y?
Izaak

55

Uwaga: poniższe informacje są przede wszystkim wynikiem moich własnych eksperymentów w React Native 0.50. W ScrollViewdokumentacji brakuje obecnie wielu informacji przedstawionych poniżej; na przykład onScrollEndDragjest całkowicie nieudokumentowany. Ponieważ wszystko tutaj opiera się na nieudokumentowanym zachowaniu, niestety nie mogę obiecać, że te informacje pozostaną poprawne za rok, a nawet za miesiąc.

Ponadto wszystko poniżej zakłada czysto pionowy widok scroll, którego offset Y nas interesuje; tłumaczenie na przesunięcia x , jeśli zajdzie taka potrzeba, jest miejmy nadzieję łatwym ćwiczeniem dla czytelnika.


Różne programy obsługi zdarzeń w ScrollViewujęciu eventi pozwalają uzyskać bieżącą pozycję przewijania za pośrednictwem event.nativeEvent.contentOffset.y. Niektóre z tych programów obsługi mają nieco inne zachowanie między systemami Android i iOS, jak opisano szczegółowo poniżej.

onScroll

W systemie Android

Odpala każdą klatkę, gdy użytkownik przewija, na każdej klatce, gdy widok przewijania przesuwa się po jej zwolnieniu, na ostatniej klatce, gdy widok przewijania zatrzymuje się, a także zawsze, gdy przesunięcie widoku przewijania zmienia się w wyniku jego ramki zmieniające się (np. z powodu rotacji z poziomego na pionowy).

Na iOS

Pożary, gdy użytkownik przeciąga lub gdy przesuwa się widok przewijania, z pewną częstotliwością określoną przez scrollEventThrottlei co najwyżej raz na klatkę, kiedy scrollEventThrottle={16}. Jeśli użytkownik zwolni widok przewijania, gdy ma on wystarczający pęd do szybowania, przewodnik onScrollrównież wystrzeli, gdy dojdzie do spoczynku po szybowaniu. Jednakże, jeśli użytkownik przeciągnie, a następnie zwolni widok przewijania, gdy jest on nieruchomy, nieonScroll ma gwarancji, że uruchomi się w końcowej pozycji, chyba że zostało ustawione tak, że uruchamia każdą klatkę przewijania.scrollEventThrottleonScroll

Ustawienie wiąże się z kosztem wydajności, scrollEventThrottle={16}który można zmniejszyć, ustawiając go na większą liczbę. Oznacza to jednak, że onScrollnie będzie odpalać każdej klatki.

onMomentumScrollEnd

Uruchamia się, gdy widok przewijania zatrzymuje się po szybowaniu. W ogóle nie uruchamia się, jeśli użytkownik zwolni widok przewijania, gdy jest nieruchomy, tak że nie ślizga się.

onScrollEndDrag

Uruchamia się, gdy użytkownik przestaje przeciągać widok przewijania - niezależnie od tego, czy widok przewijania jest nieruchomy, czy zaczyna się ślizgać.


Biorąc pod uwagę te różnice w zachowaniu, najlepszy sposób śledzenia przesunięcia zależy od konkretnych okoliczności. W najbardziej skomplikowanym przypadku (musisz obsługiwać Androida i iOS, w tym obsługiwać zmiany w ScrollViewramce ze względu na obrót, a nie chcesz akceptować spadku wydajności na Androidzie z ustawienia scrollEventThrottlena 16) i musisz obsługiwać zmienia się również zawartość w widoku przewijania, wtedy jest to właściwy cholerny bałagan.

Najprostszy przypadek jest taki, jeśli potrzebujesz tylko obsługi Androida; po prostu użyj onScroll:

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

Aby dodatkowo obsługiwać iOS, jeśli z przyjemnością uruchamiasz program onScrollobsługi każdą klatkę i akceptujesz wpływ na wydajność, a jeśli nie musisz obsługiwać zmian ramek, jest to tylko trochę bardziej skomplikowane:

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

Aby zmniejszyć narzut wydajności na iOS, jednocześnie gwarantując, że rejestrujemy dowolną pozycję, na której ustala się widok przewijania, możemy zwiększyć scrollEventThrottlei dodatkowo zapewnić onScrollEndDragobsługę:

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

Ale jeśli chcemy obsłużyć zmiany ramek (np. Ponieważ pozwalamy na obracanie urządzenia, zmieniając dostępną wysokość ramki widoku scroll) i / lub zmiany treści, to musimy dodatkowo zaimplementować oba onContentSizeChangei onLayoutśledzić wysokość obu ramka widoku przewijania i jej zawartość, a tym samym nieustannie obliczaj maksymalne możliwe przesunięcie i wnioskuj, kiedy przesunięcie zostało automatycznie zmniejszone z powodu zmiany rozmiaru ramki lub zawartości:

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

Tak, to dość przerażające. Nie jestem też w 100% pewien, że zawsze będzie działać dobrze w przypadkach, gdy jednocześnie zmieniasz rozmiar ramki i zawartość widoku przewijania. Ale to najlepsze, co mogę wymyślić, i dopóki ta funkcja nie zostanie dodana w ramach samej struktury , myślę, że jest to najlepsze, co każdy może zrobić.


19

Odpowiedź Brada Oylera jest prawidłowa. Ale otrzymasz tylko jedno wydarzenie. Jeśli chcesz otrzymywać ciągłe aktualizacje pozycji przewijania, ustaw scrollEventThrottlerekwizyt w następujący sposób:

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

I program obsługi zdarzeń:

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

Pamiętaj, że możesz napotkać problemy z wydajnością. Odpowiednio wyreguluj przepustnicę. 16 zapewnia najwięcej aktualizacji. 0 tylko jeden.


12
To jest niepoprawne. Po pierwsze, scrollEvenThrottle działa tylko na iOS, a nie na Androida. Po drugie, reguluje tylko częstotliwość otrzymywania połączeń przewijanych. Domyślnie jest to raz na klatkę, a nie jedno zdarzenie. 1 daje więcej aktualizacji, a 16 mniej. 0 jest wartością domyślną. Ta odpowiedź jest całkowicie błędna. facebook.github.io/react-native/docs/…
Teodors

1
Odpowiedziałem na to, zanim Android był obsługiwany przez React Native, więc tak, odpowiedź dotyczy tylko iOS. Wartością domyślną jest zero, co powoduje, że zdarzenie przewijania jest wysyłane tylko raz przy każdym przewijaniu widoku.
cutemachine

1
Jaka byłaby notacja es6 dla funkcji (zdarzenie: obiekt) {}?
cbartondock

1
Po prostu function(event) { }.
cutemachine

1
@Teodors Chociaż ta odpowiedź nie dotyczy Androida, reszta twojej krytyki jest całkowicie zdezorientowana. scrollEventThrottle={16}nie nie daje „less” aktualizacje; dowolna liczba z zakresu 1-16 daje maksymalną możliwą szybkość aktualizacji. Wartość domyślna 0 daje (w iOS) jedno zdarzenie, gdy użytkownik zaczyna przewijać, a następnie nie ma kolejnych aktualizacji (co jest dość bezużyteczne i prawdopodobnie jest błędem); domyślnie nie jest to „raz na klatkę”. Oprócz tych dwóch błędów w twoim komentarzu, twoje inne twierdzenia nie zaprzeczają temu, co mówi odpowiedź (chociaż wydaje ci się, że tak jest).
Mark Amery,

7

Jeśli chcesz po prostu uzyskać bieżącą pozycję (np. Po naciśnięciu jakiegoś przycisku), zamiast śledzić ją za każdym razem, gdy użytkownik przewinie, wywołanie onScrollsłuchacza spowoduje problemy z wydajnością. Obecnie najbardziej wydajnym sposobem uzyskania aktualnej pozycji przewijania jest użycie pakietu react-native-invoke . Jest przykład na to dokładnie, ale pakiet robi wiele innych rzeczy.

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


2
Czy wiesz, czy istnieje teraz bardziej przejrzysty (nieco mniej skomplikowany) sposób żądania przesunięcia, czy jest to nadal najlepszy sposób na zrobienie tego, o którym wiesz? (Zastanawiam się, dlaczego ta odpowiedź nie jest pozytywna, ponieważ jest jedyną, która wydaje się mieć sens)
cglacet

1
Paczki już nie ma, czy gdzieś się poruszyła? Github pokazuje 404.
Noitidart

6

Aby uzyskać x / y po zakończeniu przewijania zgodnie z żądaniem oryginalnego pytania, najprostszym sposobem jest prawdopodobnie to:

<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; onMomentumScrollEndjest wyzwalane tylko wtedy, gdy widok przewijania nabrał rozpędu, gdy użytkownik go puści. Jeśli zwolnią się, gdy widok przewijania się nie porusza, nigdy nie uzyskasz żadnego ze zdarzeń pędu.
Mark Amery,

1
Przetestowałem onMomentumScrollEnd i nie mogłem go wywołać, próbowałem przewijać na różne sposoby, próbowałem zwolnić dotyk, gdy zwój był w końcowej pozycji i również został uruchomiony. Więc ta odpowiedź jest poprawna, o ile mogłem ją przetestować.
fermmm

6

Powyższe odpowiedzi powiedzieć jak uzyskać pozycję przy użyciu różnych API onScroll, onMomentumScrollEndetc; Jeśli chcesz poznać indeks strony, możesz go obliczyć na podstawie wartości przesunięcia.

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

wprowadź opis obrazu tutaj

W systemie iOS relacja między ScrollView a widocznym regionem jest następująca: Zdjęcie pochodzi z https://www.objc.io/issues/3-views/scroll-view/

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


Jeśli chcesz uzyskać „indeks” bieżącej pozycji, to jest poprawna odpowiedź. Bierzesz event.nativeEvent.contentOffset.x / deviceWidth. Dzięki za pomoc!
Sara Inés Calderón

1

W ten sposób zakończyło się wyświetleniem bieżącej pozycji przewijania na żywo z widoku. Wszystko, co robię, to korzystanie z odbiornika zdarzeń on scroll i scrollEventThrottle. Następnie przekazuję to jako zdarzenie do obsługi utworzonej przeze mnie funkcji przewijania i aktualizacji moich właściwości.

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

czy warto aktualizować stan w onScroll, biorąc pod uwagę wydajność?
Hrishi

1

użycie onScroll wchodzi w nieskończoną pętlę. Zamiast tego można użyć onMomentumScrollEnd lub onScrollEndDrag


0

Jeśli chodzi o stronę, pracuję nad komponentem wyższego rzędu, który używa w zasadzie powyższych metod, aby to zrobić. W rzeczywistości zajmuje to tylko trochę czasu, gdy dojdziesz do subtelności, takich jak początkowy układ i zmiany treści. Nie twierdzę, że zrobiłem to „poprawnie”, ale w pewnym sensie rozważałbym poprawną odpowiedź, używając komponentu, który robi to ostrożnie i konsekwentnie.

Zobacz: React-native-paged-scroll-view . Chciałbym otrzymać informację zwrotną, nawet jeśli zrobiłem wszystko źle!


1
to jest niesamowite. dziękuję za zrobienie tego. od razu uznałem to za bardzo przydatne.
Marty Cortez

Tak szczęśliwy! Naprawdę doceniam opinie!

1
Przepraszające -1, ponieważ projekt otwarcie przyznaje, że jest niekonserwowany i że komponent ma „kilka brzydkich błędów” .
Mark Amery

Dzięki za opinię @MarkAmery. :) Znalezienie czasu na open source nie jest łatwe.

Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.