Dapatkan posisi scroll ScrollView saat ini di React Native

115

Apakah mungkin untuk mendapatkan posisi scroll saat ini, atau halaman saat ini dari <ScrollView>komponen di React Native?

Jadi sesuatu seperti:

<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>
Sang
sumber
Relevan: masalah GitHub yang menyarankan penambahan cara mudah untuk mendapatkan informasi ini.
Mark Amery

Jawaban:

207

Mencoba.

<ScrollView onScroll={this.handleScroll} />

Lalu:

handleScroll: function(event: Object) {
 console.log(event.nativeEvent.contentOffset.y);
},
brad oyler
sumber
5
Ini tidak selalu aktif saat nilai gulir berubah. Jika misalnya scrollview diubah ukurannya sehingga sekarang dapat menampilkan semuanya di layar sekaligus, Anda sepertinya tidak selalu mendapatkan acara onScroll yang memberi tahu Anda.
Thomas Parslow
19
Atau event.nativeEvent.contentOffset.xjika Anda memiliki ScrollView horizontal;) Terima kasih!
Tieme
2
Ini juga bisa kita gunakan di Android.
Janaka Pushpakumara
4
Dengan putus asa, kecuali Anda menyetel scrollEventThrottleke 16 atau lebih rendah (untuk mendapatkan acara untuk setiap bingkai), tidak ada jaminan bahwa ini akan melaporkan offset pada bingkai terakhir - artinya untuk memastikan bahwa Anda memiliki offset yang tepat setelah pengguliran selesai, Anda perlu mengatur scrollEventThrottle={16}dan menerima dampak kinerja itu.
Mark Amery
@bradoyler: apakah mungkin untuk mengetahui nilai maksimum event.nativeEvent.contentOffset.y?
Isaac
55

Penafian: yang berikut ini terutama merupakan hasil eksperimen saya sendiri di React Native 0.50. The ScrollViewdokumentasi saat ini hilang banyak informasi yang tercakup di bawah ini; misalnya onScrollEndDragsama sekali tidak terdokumentasi. Karena semua yang ada di sini bergantung pada perilaku yang tidak terdokumentasi, sayangnya saya tidak dapat berjanji bahwa informasi ini akan tetap benar setahun atau bahkan sebulan dari sekarang.

Juga, segala sesuatu di bawah ini mengasumsikan tampilan gulir vertikal murni yang offset y nya kita minati; menterjemahkan ke x offset, bila diperlukan semoga menjadi latihan yang mudah bagi pembaca.


Berbagai penangan acara sedang ScrollViewmengambil eventdan membiarkan Anda mendapatkan posisi gulir saat ini melalui event.nativeEvent.contentOffset.y. Beberapa penangan ini memiliki perilaku yang sedikit berbeda antara Android dan iOS, seperti yang dijelaskan di bawah ini.

onScroll

Di Android

Menyalakan setiap bingkai saat pengguna menggulir, pada setiap bingkai saat tampilan gulir meluncur setelah pengguna melepaskannya, pada bingkai terakhir saat tampilan gulir berhenti, dan juga setiap kali offset tampilan gulir berubah sebagai akibat dari bingkainya berubah (misalnya karena rotasi dari lanskap ke potret).

Di iOS

Kebakaran saat pengguna menyeret atau saat tampilan gulir meluncur, pada frekuensi tertentu yang ditentukan oleh scrollEventThrottledan paling banyak sekali per frame saat scrollEventThrottle={16}. Jika pengguna melepaskan tampilan gulir saat memiliki cukup momentum untuk meluncur, onScrollpenangan juga akan aktif saat berhenti setelah meluncur. Namun, jika pengguna menyeret dan kemudian melepaskan pandangan gulir sementara itu stasioner, onScrollyang tidak dijamin api untuk posisi akhir kecuali scrollEventThrottletelah diatur sedemikian rupa sehinggaonScroll kebakaran setiap frame bergulir.

Ada biaya kinerja untuk pengaturan scrollEventThrottle={16}yang dapat dikurangi dengan mengaturnya ke angka yang lebih besar. Namun, ini berarti onScrolltidak akan mengaktifkan setiap frame.

onMomentumScrollEnd

Kebakaran saat tampilan gulir berhenti setelah meluncur. Tidak menyala sama sekali jika pengguna melepaskan tampilan gulir saat tidak bergerak sehingga tidak meluncur.

onScrollEndDrag

Kebakaran saat pengguna berhenti menyeret tampilan gulir - terlepas dari apakah tampilan gulir tidak bergerak atau mulai meluncur.


Mengingat perbedaan perilaku ini, cara terbaik untuk melacak penggantian kerugian bergantung pada keadaan Anda yang sebenarnya. Dalam kasus yang paling rumit (Anda perlu mendukung Android dan iOS, termasuk menangani perubahan dalam ScrollViewbingkai karena rotasi, dan Anda tidak ingin menerima penalti kinerja pada Android dari pengaturan scrollEventThrottleke 16), dan Anda perlu menangani mengubah konten dalam tampilan gulir juga, maka itu benar-benar berantakan.

Kasus paling sederhana adalah jika Anda hanya perlu menangani Android; gunakan saja onScroll:

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

Untuk lebih mendukung iOS, jika Anda senang untuk mengaktifkan onScrollhandler setiap frame dan menerima implikasi kinerja dari itu, dan jika Anda tidak perlu menangani perubahan frame, maka ini hanya sedikit lebih rumit:

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

Untuk mengurangi overhead kinerja di iOS sambil tetap menjamin bahwa kami merekam posisi apa pun yang ditetapkan oleh tampilan gulir, kami dapat meningkatkan scrollEventThrottledan juga menyediakan onScrollEndDragpenangan:

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

Tetapi jika kita ingin menangani perubahan bingkai (misalnya karena kita mengizinkan perangkat untuk diputar, mengubah ketinggian yang tersedia untuk bingkai tampilan gulir) dan / atau perubahan konten, maka kita harus menerapkan keduanya onContentSizeChangedan onLayoutuntuk melacak ketinggian keduanya. bingkai tampilan gulir dan kontennya, dan dengan demikian terus menghitung offset maksimum yang mungkin dan menyimpulkan saat offset secara otomatis berkurang karena bingkai atau ukuran konten berubah:

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

Ya, itu sangat mengerikan. Saya juga tidak 100% yakin bahwa ini akan selalu berfungsi dengan benar jika Anda secara bersamaan mengubah ukuran bingkai dan konten tampilan gulir. Tapi itu yang terbaik yang bisa saya hasilkan, dan sampai fitur ini ditambahkan dalam kerangka itu sendiri , saya pikir ini adalah yang terbaik yang bisa dilakukan siapa pun.

Mark Amery
sumber
19

Jawaban Brad Oyler benar. Tetapi Anda hanya akan menerima satu acara. Jika Anda perlu mendapatkan pembaruan konstan dari posisi gulir, Anda harus mengatur scrollEventThrottleprop, seperti ini:

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

Dan event handler:

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

Ketahuilah bahwa Anda mungkin mengalami masalah kinerja. Sesuaikan throttle dengan benar. 16 memberi Anda pembaruan paling banyak. 0 hanya satu.

mesin potong
sumber
12
Ini salah Pertama, scrollEvenThrottle hanya berfungsi untuk iOS dan bukan untuk android. Kedua, ini hanya mengatur seberapa sering Anda menerima panggilan onScroll. Default-nya adalah sekali per frame, bukan satu acara. 1 memberi Anda lebih banyak pembaruan dan 16 memberi Anda lebih sedikit. 0 adalah default. Jawaban ini sepenuhnya salah. facebook.github.io/react-native/docs/…
Teodors
1
Saya menjawab ini sebelum Android didukung oleh React Native, jadi ya, jawabannya hanya berlaku untuk iOS. Nilai defaultnya adalah nol, yang mengakibatkan acara gulir dikirim hanya sekali setiap kali tampilan di-scroll.
cutemachine
1
Apa yang akan menjadi notasi es6 untuk fungsi (event: Object) {}?
cbartondock
1
Cuma function(event) { }.
cutemachine
1
@Teodors Meskipun jawaban ini tidak mencakup Android, kritik Anda lainnya benar-benar membingungkan. scrollEventThrottle={16}tidak tidak memberikan "kurang" update; nomor apa pun dalam kisaran 1-16 memberi Anda tingkat pembaruan semaksimal mungkin. Default 0 memberi Anda (di iOS) satu peristiwa ketika pengguna mulai menggulir dan kemudian tidak ada pembaruan berikutnya (yang sangat tidak berguna dan mungkin bug); defaultnya bukan "sekali per frame". Selain dua kesalahan dalam komentar Anda, pernyataan Anda yang lain tidak bertentangan dengan apa pun yang dikatakan jawabannya (meskipun Anda sepertinya berpikir demikian).
Mark Amery
7

Jika Anda hanya ingin mendapatkan posisi saat ini (misalnya ketika beberapa tombol ditekan) daripada melacaknya setiap kali pengguna menggulir, maka memanggil onScrollpendengar akan menyebabkan masalah kinerja. Saat ini, cara paling berkinerja untuk mendapatkan posisi scroll saat ini menggunakan react-native-invoke . Ada contoh untuk hal ini, tetapi paket melakukan banyak hal lainnya.

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

Teodors
sumber
2
Apakah Anda tahu jika ada cara yang lebih bersih (sedikit lebih mudah) untuk meminta penggantian kerugian, atau apakah ini masih cara terbaik untuk melakukannya yang Anda ketahui? (Saya bertanya-tanya mengapa jawaban ini tidak disukai karena ini adalah satu-satunya yang terlihat masuk akal)
cglacet
1
Paket sudah tidak ada lagi, apakah dipindahkan ke suatu tempat? Github menunjukkan 404.
Noitidart
6

Untuk mendapatkan x / y setelah gulir berakhir seperti yang diminta oleh pertanyaan asli, cara termudah mungkin adalah ini:

<ScrollView
   horizontal={true}
   pagingEnabled={true}
   onMomentumScrollEnd={(event) => { 
      // scroll animation ended
      console.log(e.nativeEvent.contentOffset.x);
      console.log(e.nativeEvent.contentOffset.y);
   }}>
   ...content
</ScrollView>
j0n
sumber
-1; onMomentumScrollEndhanya dipicu jika tampilan gulir memiliki beberapa momentum saat pengguna melepaskannya. Jika mereka melepaskan saat tampilan gulir tidak bergerak, Anda tidak akan pernah mendapatkan peristiwa momentum apa pun.
Mark Amery
1
Saya menguji onMomentumScrollEnd dan tidak mungkin bagi saya untuk tidak memicunya, saya mencoba menggulir dengan cara yang berbeda, saya mencoba melepaskan sentuhan ketika gulungan berada di posisi akhir dan itu juga dipicu. Jadi jawaban ini benar sejauh yang saya bisa mengujinya.
fermmm
6

Jawaban di atas menjelaskan bagaimana mendapatkan posisi menggunakan API yang berbeda onScroll, onMomentumScrollEnddll; Jika Anda ingin mengetahui indeks halaman, Anda dapat menghitungnya menggunakan nilai 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
   }
 };

masukkan deskripsi gambar di sini

Di iOS, hubungan antara ScrollView dan wilayah yang terlihat adalah sebagai berikut: Gambar itu berasal dari https://www.objc.io/issues/3-views/scroll-view/

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

RY Zheng
sumber
Jika Anda ingin mendapatkan "indeks" item saat ini, ini adalah jawaban yang benar. Anda mengambil event.nativeEvent.contentOffset.x / deviceWidth. Terima kasih atas bantuan Anda!
Sara Inés Calderon
1

Ini adalah bagaimana akhirnya mendapatkan posisi gulir saat ini langsung dari tampilan. Yang saya lakukan hanyalah menggunakan pendengar acara gulir dan scrollEventThrottle. Saya kemudian meneruskannya sebagai acara untuk menangani fungsi gulir yang saya buat dan memperbarui alat peraga saya.

 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} />
    )
    }
    }
Sabba Keynejad
sumber
apakah baik untuk memperbarui status di onScroll dengan mempertimbangkan performa?
Hrishi
1

penggunaan onScroll memasuki putaran tak terbatas. onMomentumScrollEnd atau onScrollEndDrag dapat digunakan sebagai gantinya

kmehmet
sumber
0

Adapun halaman, saya sedang mengerjakan komponen tingkat tinggi yang pada dasarnya menggunakan metode di atas untuk melakukan hal ini. Ini sebenarnya hanya membutuhkan sedikit waktu ketika Anda sampai ke seluk-beluk seperti tata letak awal dan perubahan konten. Saya tidak akan mengklaim telah melakukannya dengan 'benar', tetapi dalam arti tertentu saya akan mempertimbangkan jawaban yang benar untuk menggunakan komponen yang melakukan ini dengan hati-hati dan konsisten.

Lihat: react-native-paged-scroll-view . Akan menyukai umpan balik, bahkan jika saya telah melakukan semuanya dengan salah!


sumber
1
ini luar biasa. terimakasih telah membuat ini. saya menemukan ini sangat berguna segera.
Marty Cortez
Senang sekali! Sangat menghargai umpan baliknya!
1
Sebuah permintaan maaf -1, karena proyek secara terbuka mengakui bahwa itu tidak dirawat dan bahwa komponen tersebut memiliki "beberapa bug jelek" .
Mark Amery
Terima kasih atas umpan baliknya @MarkAmery. :) Menemukan waktu untuk open source tidaklah mudah.
-3

Saya yakin contentOffsetakan memberi Anda objek yang berisi scroll offset kiri atas:

http://facebook.github.io/react-native/docs/scrollview.html#contentoffset

Colin Ramsay
sumber
4
Bukankah ini seorang penyetel, bukan pengambil?
Aaron Wang
1
-1 ini tidak masuk akal; dokumentasi yang Anda tautkan adalah untuk prop JSX, bukan properti JavaScript, dan hanya dapat digunakan untuk menyetel offset, bukan untuk mendapatkannya.
Mark Amery