React Native'de ScrollView'ın geçerli kaydırma konumunu alın


115

<ScrollView>React Native'deki mevcut kaydırma konumunu veya bir bileşenin geçerli sayfasını almak mümkün mü ?

Yani şöyle bir şey:

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

Alakalı: Bu bilgiyi almanın uygun bir yolunu eklemeyi öneren bir GitHub sorunu .
Mark Amery

Yanıtlar:


208

Deneyin.

<ScrollView onScroll={this.handleScroll} />

Ve sonra:

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

5
Kaydırma değeri değiştiğinde bu her zaman ateşlenmez. Örneğin, kaydırma görünümü yeniden boyutlandırılırsa, artık ekrandaki her şeyi aynı anda görüntüleyebiliyorsa, her zaman size söyleyen bir onScroll olayını görmezsiniz.
Thomas Parslow

19
Veya event.nativeEvent.contentOffset.xyatay bir ScrollView'unuz varsa;) Teşekkürler!
Tieme

2
Bunu Android'de de kullanabiliriz.
Janaka Pushpakumara

4
Sinir bozucu bir şekilde, scrollEventThrottle16 veya daha düşük bir değere ayarlamadığınız sürece (her kare için bir etkinlik almak için), bunun son karede ofseti rapor edeceğinin garantisi yoktur - bu, kaydırma bittikten sonra doğru ofsete sahip olduğunuzdan emin olmanız anlamına gelir. scrollEventThrottle={16}bunun performans etkisini belirlemeniz ve kabul etmeniz gerekir .
Mark Amery

@bradoyler: maksimum değerini bilmek mümkün event.nativeEvent.contentOffset.ymü?
Isaac

55

Sorumluluk reddi: Aşağıdakiler öncelikle React Native 0.50'deki kendi deneyimlerimin sonucudur. ScrollViewDokümantasyon , şu anda aşağıda kaplı birçok bilgi yoktur; örneğin onScrollEndDragtamamen belgelenmemiş. Buradaki her şey belgelenmemiş davranışa dayandığından, maalesef bu bilginin şu andan itibaren bir yıl hatta bir ay sonra doğru kalacağına dair hiçbir söz veremem.

Ayrıca, aşağıdaki her şey y ofseti ilgilendiğimiz tamamen dikey bir kaydırma görünümü varsayar ; Gerekirse x ofsetlere çevirmek umarım okuyucu için kolay bir alıştırmadır.


Çeşitli olay işleyicileri ScrollViewalır eventve geçerli kaydırma konumunu üzerinden almanızı sağlar event.nativeEvent.contentOffset.y. Bu işleyicilerden bazıları, aşağıda ayrıntılı olarak açıklandığı üzere Android ve iOS arasında biraz farklı davranışa sahiptir.

onScroll

Android'de

Kullanıcı kaydırırken her kareyi, kullanıcı serbest bıraktıktan sonra kaydırma görünümü kayarken her karede, kaydırma görünümü durduğunda son karede ve ayrıca, çerçevesinin bir sonucu olarak kaydırma görünümünün ofseti değiştiğinde her kareyi ateşler değişiyor (örneğin, yataydan portreye dönüş nedeniyle).

İOS'ta

Kullanıcı sürüklerken veya kaydırma görünümü kayarken, tarafından belirlenen sıklıkta scrollEventThrottleve en fazla bir kez ne zaman çerçeve başına bir kez tetiklenir scrollEventThrottle={16}. Kullanıcı, kaymak için yeterli momentuma sahipken kaydırma görünümünü serbest bırakırsa , onScrollişleyici, kaymadan sonra dinlenmeye geldiğinde de ateşlenecektir. Kullanıcı sürükler ve sonra sabitken kaydırma görünüm bırakır, ancak onScrollbir değil sürece nihai konumuna yangına garanti scrollEventThrottlegibi ayarlanmış olduğunu onScrollyangınlar kaydırma her çerçevesi.

scrollEventThrottle={16}Daha büyük bir sayıya ayarlanarak azaltılabilecek bir performans maliyeti vardır . Ancak bu, onScrollher kareyi ateşlemeyeceği anlamına gelir .

onMomentumScrollEnd

Kaydırdıktan sonra kaydırma görünümü durduğunda ateşlenir. Kullanıcı kaydırma görünümünü sabitken kaymayacak şekilde serbest bırakırsa hiç çalışmaz.

onScrollEndDrag

Kaydırma görünümünün sabit kaldığından veya kaymaya başladığından bağımsız olarak, kullanıcı kaydırma görünümünü sürüklemeyi bıraktığında tetiklenir.


Davranıştaki bu farklılıklar göz önüne alındığında, ofseti takip etmenin en iyi yolu koşullarınıza bağlıdır. En karmaşık durumda ( ScrollViewdöndürme nedeniyle çerçevesindeki değişiklikleri işleme dahil olmak üzere Android ve iOS'u desteklemeniz gerekir ve Android'de ayarlardan scrollEventThrottle16'ya kadar olan performans cezasını kabul etmek istemezsiniz) ve bununla başa çıkmanız gerekir kaydırma görünümündeki içerikte de değişiklik olursa , bu tam bir karmaşa.

En basit durum, yalnızca Android'i kullanmanız gerektiğidir; sadece şunu kullan onScroll:

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

Ayrıca iOS desteklemek için, eğer sen sizsiniz, yangın mutlu onScrollHandler her karesini ve performans etkileri kabul edin ve eğer sen sap çerçeve değişiklikleri gerekmez, o zaman biraz daha karmaşık bit yalnızca var:

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

Kaydırma görünümünün yerleştiği herhangi bir konumu kaydetmemizi garanti ederken, iOS'taki performans ek yükünü azaltmak için artırabilir scrollEventThrottleve ek olarak bir onScrollEndDragişleyici sağlayabiliriz :

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

Ancak çerçeve değişikliklerini (örneğin, cihazın döndürülmesine izin verdiğimiz için, kaydırma görünümünün çerçevesi için mevcut yüksekliği değiştirdiğimiz için) ve / veya içerik değişikliklerini işlemek istiyorsak, ek olarak her ikisini de uygulamalı onContentSizeChangeve onLayouther ikisinin de yüksekliğini takip etmeliyiz kaydırma görünümünün çerçevesi ve içeriği ve böylece sürekli olarak olası maksimum ofseti hesaplayın ve çerçeve veya içerik boyutu değişikliği nedeniyle ofsetin otomatik olarak ne zaman azaldığını tahmin edin:

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

Evet, oldukça korkunç. Ayrıca , kaydırma görünümünün hem çerçevesinin hem de içeriğinin boyutunu aynı anda değiştirdiğiniz durumlarda her zaman doğru çalışacağından% 100 emin değilim . Ancak bulabildiğim en iyisi bu ve bu özellik çerçevenin kendisine eklenene kadar , bence bu herkesin yapabileceği en iyisi.


19

Brad Oyler'in cevabı doğru. Ancak yalnızca bir etkinlik alacaksınız. Kaydırma konumu için sürekli güncelleme almanız gerekiyorsa, scrollEventThrottlepervaneyi şu şekilde ayarlamalısınız :

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

Ve olay işleyicisi:

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

Performans sorunlarıyla karşılaşabileceğinizi unutmayın. Gazı buna göre ayarlayın. 16 size en çok güncellemeyi verir. 0 sadece bir.


12
Bu yanlış. Birincisi, scrollEvenThrottle yalnızca iOS için çalışır, android için değil. İkinci olarak, yalnızca onScroll çağrılarını ne sıklıkla alacağınızı düzenler. Varsayılan, tek bir olay değil, kare başına bir defadır. 1 size daha fazla güncelleme, 16 ise daha az güncelleme sağlar. 0 varsayılandır. Bu cevap tamamen yanlış. facebook.github.io/react-native/docs/…
Teodors

1
Bunu, Android React Native tarafından desteklenmeden önce yanıtladım, bu yüzden evet, yanıt yalnızca iOS için geçerli. Varsayılan değer sıfırdır, bu da kaydırma olayının, görünüm her kaydırıldığında yalnızca bir kez gönderilmesine neden olur.
cutemachine

1
İşlev (olay: Nesne) {} için es6 gösterimi ne olurdu?
cbartondock

1
Sadece function(event) { }.
cutemachine

1
@Teodors Bu cevap Android'i kapsamazken, eleştirinizin geri kalanı tamamen karışık. scrollEventThrottle={16}yok değil size "daha az" güncellemeleri vermek; 1-16 aralığındaki herhangi bir sayı size mümkün olan en yüksek güncelleme hızını verir. Varsayılan 0, size (iOS'ta) kullanıcı kaydırmaya başladığında bir olay verir ve ardından herhangi bir güncelleme yapılmaz (bu oldukça işe yaramaz ve muhtemelen bir hatadır); varsayılan değil "her kare". Yorumunuzdaki bu iki hatanın yanı sıra, diğer iddialarınız yanıtın söylediği hiçbir şeyle çelişmiyor (yine de öyle olduğunu düşünüyorsunuz).
Mark Amery

7

Kullanıcı her kaydırdığında onu izlemek yerine sadece mevcut konumu (örneğin bir düğmeye basıldığında) almak istiyorsanız, bir onScrolldinleyiciyi çağırmak performans sorunlarına neden olacaktır. Şu anda mevcut kaydırma konumunu almanın en performanslı yolu react-native-invoke paketini kullanmaktır. Bunun tam olarak bir örneği var, ancak paket birçok başka şey yapıyor.

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


2
Şu anda ofset istemek için daha temiz bir yolun (biraz daha zahmetli) olup olmadığını biliyor musunuz, yoksa bunu yapmanın hala en iyi yolu bu mu? (Mantıklı görünen tek cevap olduğu için neden bu cevaba olumlu oy verilmediğini merak ediyorum)
cglacet

1
Paket artık orada değil, bir yere taşınmış mı? Github
404'ü

6

Kaydırma sona erdikten sonra, orijinal soruların sorduğu şekilde x / y'yi elde etmek için en kolay yol muhtemelen şudur:

<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; onMomentumScrollEndyalnızca kaydırma görünümünün, kullanıcı bıraktığında bir ivme kazanması durumunda tetiklenir. Kaydırma görünümü hareket etmiyorken serbest kalırlarsa, momentum olaylarının hiçbirini asla alamazsınız.
Mark Amery

1
MomentumScrollEnd'de test ettim ve tetiklememem imkansızdı, farklı şekillerde kaydırmayı denedim, kaydırma son konumdayken dokunuşu bırakmayı denedim ve aynı zamanda tetiklendi. Yani bu cevap, test edebileceğim kadarıyla doğru.
fermmm

6

Yukarıdaki cevaplar, farklı API onScroll, onMomentumScrollEndvb. Kullanarak konumun nasıl alınacağını anlatır ; Sayfa indeksini bilmek istiyorsanız, ofset değerini kullanarak bunu hesaplayabilirsiniz.

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

görüntü açıklamasını buraya girin

İOS'ta ScrollView ile görünür bölge arasındaki ilişki aşağıdaki gibidir: Resim https://www.objc.io/issues/3-views/scroll-view/ adresinden geliyor.

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


Mevcut öğenin "indeksini" almak istiyorsanız, bu doğru cevaptır. Event.nativeEvent.contentOffset.x / deviceWidth alırsınız. Yardımınız için teşekkürler!
Sara Inés Calderon

1

Bu, mevcut kaydırma konumunu görünümden canlı olarak almanın sonucudur. Tek yaptığım on scroll olay dinleyicisini ve scrollEventThrottle'ı kullanmak. Daha sonra bunu yaptığım kaydırma işlevini işlemek ve sahne öğelerimi güncellemek için bir olay olarak geçiriyorum.

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

performansı göz önünde bulundurarak onScroll'daki durumu güncellemek iyi mi?
Hrishi

1

onScroll kullanımı sonsuz döngüye girer. bunun yerine onMomentumScrollEnd veya onScrollEndDrag kullanılabilir


0

Sayfaya gelince, tam olarak bunu yapmak için temelde yukarıdaki yöntemleri kullanan daha yüksek bir bileşen üzerinde çalışıyorum. İlk düzen ve içerik değişiklikleri gibi inceliklere indiğinizde aslında biraz zaman alır. Bunu 'doğru' yaptığımı iddia etmeyeceğim, ancak bir anlamda, bunu dikkatli ve tutarlı bir şekilde yapan bileşeni kullanmak için doğru cevabı düşünürdüm.

Bakınız: react-native-paged-scroll-view . Tamamen yanlış yapmış olsam bile geri bildirim almak isterim!


1
bu harika. Bunu yaptığınız için teşekkür ederim. bunu hemen çok faydalı buldum.
Marty Cortez

Çok mutlu! Geri bildirimler için gerçekten minnettarız!

1
Özür dileyen bir -1, çünkü proje açıkça bakılmadığını ve bileşende "birkaç çirkin böcek" olduğunu kabul ediyor .
Mark Amery

@MarkAmery geri bildiriminiz için teşekkürler. :) Açık kaynak için zaman bulmak kolay değil.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.