Nhận vị trí cuộn hiện tại của ScrollView trong React Native


115

Có thể lấy vị trí cuộn hiện tại hoặc trang hiện tại của một <ScrollView>thành phần trong React Native không?

Vì vậy, một cái gì đó như:

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

Có liên quan: vấn đề GitHub đề xuất thêm một cách thuận tiện để lấy thông tin này.
Mark Amery

Câu trả lời:


207

Thử.

<ScrollView onScroll={this.handleScroll} />

Và sau đó:

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

5
Điều này không phải lúc nào cũng kích hoạt khi giá trị cuộn thay đổi. Ví dụ: nếu chế độ xem cuộn được thay đổi kích thước, khiến nó bây giờ có thể hiển thị toàn bộ nội dung trên màn hình cùng một lúc, bạn dường như không phải lúc nào cũng nhận được sự kiện onScroll cho bạn biết.
Thomas Parslow

19
Hoặc event.nativeEvent.contentOffset.xnếu bạn có ScrollView ngang;) Cảm ơn!
Tieme

2
Điều này chúng tôi cũng có thể sử dụng trong Android.
Janaka Pushpakumara

4
Thật đáng buồn, trừ khi bạn đặt scrollEventThrottlethành 16 hoặc thấp hơn (để có một sự kiện cho mỗi khung hình), không có gì đảm bảo rằng điều này sẽ báo cáo mức bù trên khung cuối cùng - có nghĩa là để đảm bảo rằng bạn có mức bù phù hợp sau khi cuộn xong, bạn cần thiết lập scrollEventThrottle={16}và chấp nhận tác động hiệu suất của điều đó.
Mark Amery

@bradoyler: có thể biết giá trị lớn nhất của event.nativeEvent.contentOffset.y?
Isaac

55

Tuyên bố từ chối trách nhiệm: những gì tiếp theo chủ yếu là kết quả của thử nghiệm của riêng tôi trong React Native 0.50. Các ScrollViewtài liệu hiện đang thiếu rất nhiều thông tin đề cập dưới đây; chẳng hạn onScrollEndDraglà hoàn toàn không có giấy tờ. Vì mọi thứ ở đây đều dựa trên hành vi không có giấy tờ, rất tiếc là tôi không thể đưa ra lời hứa rằng thông tin này sẽ vẫn chính xác sau một năm hoặc thậm chí một tháng kể từ bây giờ.

Ngoài ra, mọi thứ bên dưới giả định một chế độ xem cuộn dọc hoàn toàn có độ lệch y mà chúng tôi quan tâm; dịch sang hiệu số x , nếu cần, hy vọng là một bài tập dễ dàng cho người đọc.


Các trình xử lý sự kiện khác nhau khi ScrollViewthực hiện eventvà cho phép bạn có được vị trí cuộn hiện tại qua event.nativeEvent.contentOffset.y. Một số trình xử lý này có hành vi hơi khác nhau giữa Android và iOS, như chi tiết bên dưới.

onScroll

Trên Android

Kích hoạt mọi khung hình khi người dùng đang cuộn, trên mọi khung hình trong khi chế độ xem cuộn đang lướt sau khi người dùng thả nó ra, trên khung hình cuối cùng khi chế độ xem cuộn dừng lại và cũng có thể bất cứ khi nào độ lệch của chế độ xem cuộn thay đổi do khung của nó thay đổi (ví dụ: do xoay từ ngang sang dọc).

Trên iOS

Kích hoạt khi người dùng đang kéo hoặc trong khi chế độ xem cuộn đang lướt, ở một số tần suất được xác định bởi scrollEventThrottlevà nhiều nhất một lần trên mỗi khung hình khi nào scrollEventThrottle={16}. Nếu người dùng nhả chế độ xem cuộn trong khi nó có đủ động lượng để lướt, onScrolltrình xử lý cũng sẽ kích hoạt khi nó nghỉ sau khi lướt. Tuy nhiên, nếu người dùng kéo và sau đó phát hành xem di chuyển trong khi nó đang đứng yên, onScrollđược không đảm bảo để lửa cho vị trí chính thức trừ khi scrollEventThrottleđã được thiết lập như vậy mà onScrollđám cháy mỗi khung di chuyển.

scrollEventThrottle={16}thể giảm chi phí hiệu suất để cài đặt bằng cách đặt nó thành một số lớn hơn. Tuy nhiên, điều này có nghĩa là điều đó onScrollsẽ không kích hoạt mọi khung hình.

onMomentumScrollEnd

Kích hoạt khi chế độ xem cuộn dừng lại sau khi lướt. Hoàn toàn không kích hoạt nếu người dùng thả chế độ xem cuộn trong khi nó đứng yên để nó không lướt đi.

onScrollEndDrag

Kích hoạt khi người dùng ngừng kéo chế độ xem cuộn - bất kể chế độ xem cuộn là đứng yên hay bắt đầu lướt.


Với những khác biệt này trong hành vi, cách tốt nhất để theo dõi sự bù đắp phụ thuộc vào hoàn cảnh cụ thể của bạn. Trong trường hợp phức tạp nhất (bạn cần hỗ trợ Android và iOS, bao gồm xử lý các thay đổi trong ScrollViewkhung của do xoay và bạn không muốn chấp nhận hình phạt hiệu suất trên Android từ cài đặt scrollEventThrottlethành 16) và bạn cần xử lý những thay đổi đối với nội dung trong chế độ xem cuộn, thì đó là một mớ hỗn độn chết tiệt.

Trường hợp đơn giản nhất là nếu bạn chỉ cần xử lý Android; chỉ cần sử dụng onScroll:

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

Để hỗ trợ thêm cho iOS, nếu bạn hài lòng với việc kích hoạt onScrolltrình xử lý mọi khung hình và chấp nhận các tác động hiệu suất của điều đó và nếu bạn không cần xử lý các thay đổi về khung hình, thì nó chỉ phức tạp hơn một chút:

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

Để giảm chi phí hiệu suất trên iOS trong khi vẫn đảm bảo rằng chúng tôi ghi lại bất kỳ vị trí nào mà chế độ xem cuộn ổn định, chúng tôi có thể tăng scrollEventThrottlevà cung cấp thêm một onScrollEndDragtrình xử lý:

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

Nhưng nếu chúng ta muốn xử lý các thay đổi về khung hình (ví dụ: vì chúng ta cho phép thiết bị được xoay, thay đổi chiều cao có sẵn cho khung của chế độ xem cuộn) và / hoặc thay đổi nội dung, thì chúng ta phải triển khai thêm cả hai onContentSizeChangeonLayoutđể theo dõi chiều cao của cả hai khung của chế độ xem cuộn và nội dung của nó, và do đó liên tục tính toán độ chênh lệch tối đa có thể và suy ra khi độ lệch được tự động giảm do thay đổi kích thước khung hoặc nội dung:

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

Vâng, nó khá kinh hoàng. Tôi cũng không chắc chắn 100% rằng nó sẽ luôn hoạt động bình thường trong trường hợp bạn đồng thời thay đổi kích thước của cả khung và nội dung của dạng xem cuộn. Nhưng đó là cách tốt nhất mà tôi có thể nghĩ ra và cho đến khi tính năng này được thêm vào trong chính khuôn khổ , tôi nghĩ đây là điều tốt nhất mà bất kỳ ai cũng có thể làm được.


19

Câu trả lời của Brad Oyler là đúng. Nhưng bạn sẽ chỉ nhận được một sự kiện. Nếu bạn cần cập nhật liên tục vị trí cuộn, bạn nên đặt giá scrollEventThrottleđỡ, như sau:

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

Và trình xử lý sự kiện:

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

Lưu ý rằng bạn có thể gặp phải các vấn đề về hiệu suất. Điều chỉnh ga phù hợp. 16 cung cấp cho bạn nhiều bản cập nhật nhất. 0 duy nhất.


12
Điều này là không đúng. Đối với một, scrollEvenThrottle chỉ hoạt động cho iOS chứ không phải cho Android. Thứ hai, nó chỉ quy định tần suất bạn nhận cuộc gọi onScroll. Mặc định là một lần cho mỗi khung hình, không phải một sự kiện. 1 cung cấp cho bạn nhiều cập nhật hơn và 16 cung cấp cho bạn ít hơn. 0 là mặc định. Câu trả lời này hoàn toàn sai. facebook.github.io/react-native/docs/…
Teodors

1
Tôi đã trả lời câu này trước khi Android được React Native hỗ trợ, vì vậy có, câu trả lời chỉ áp dụng cho iOS. Giá trị mặc định bằng 0, dẫn đến sự kiện cuộn chỉ được gửi một lần mỗi khi chế độ xem được cuộn.
cutemachine

1
Ký hiệu es6 cho hàm (event: Object) {} sẽ là gì?
cbartondock

1
Chỉ là function(event) { }.
cutemachine

1
@Teodors Mặc dù câu trả lời này không bao gồm Android, phần còn lại của lời chỉ trích của bạn là hoàn toàn nhầm lẫn. scrollEventThrottle={16}không không cung cấp cho bạn "ít" Cập nhật; bất kỳ số nào trong phạm vi 1-16 cung cấp cho bạn tỷ lệ cập nhật tối đa có thể. Mặc định là 0 cung cấp cho bạn (trên iOS) một sự kiện khi người dùng bắt đầu cuộn và sau đó không có bản cập nhật tiếp theo (điều này khá vô dụng và có thể là một lỗi); mặc định không phải là "một lần cho mỗi khung hình". Ngoài hai lỗi đó trong nhận xét của bạn, các khẳng định khác của bạn không mâu thuẫn với bất kỳ điều gì mà câu trả lời nêu ra (mặc dù bạn có vẻ nghĩ rằng chúng đúng).
Mark Amery

7

Nếu bạn chỉ muốn có được vị trí hiện tại (ví dụ: khi một số nút được nhấn) thay vì theo dõi nó bất cứ khi nào người dùng cuộn, thì việc gọi onScrollngười nghe sẽ gây ra các vấn đề về hiệu suất. Hiện tại, cách hiệu quả nhất để có được vị trí cuộn hiện tại là sử dụng gói react-native-invoke . Có một ví dụ cho điều này chính xác, nhưng gói thực hiện nhiều thứ khác.

Đọc về nó ở đây. https://medium.com/@talkol/invoke-any-native-api-directly-from-pure-javascript-in-react-native-1fb6afcdf57d#.68ls1sopd


2
Bạn có biết liệu có cách nào tốt hơn (ít hacky hơn) để yêu cầu bù đắp hay không, hay đây vẫn là cách tốt nhất mà bạn biết? (Tôi tự hỏi tại sao câu trả lời này không được bỏ phiếu tán vì nó là người duy nhất mà ngoại hình có ý nghĩa)
cglacet

1
Gói hàng không còn ở đó, nó đã chuyển đi đâu đó? Github đang chiếu 404.
Noitidart

6

Để nhận được x / y sau khi cuộn kết thúc như các câu hỏi ban đầu yêu cầu, cách dễ nhất có lẽ là:

<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; onMomentumScrollEndchỉ được kích hoạt nếu chế độ xem cuộn có một số động lượng khi người dùng buông nó ra. Nếu chúng phát hành trong khi chế độ xem cuộn không di chuyển, bạn sẽ không bao giờ nhận được bất kỳ sự kiện động lượng nào.
Mark Amery

1
Tôi đã thử nghiệm onMomentumScrollEnd và tôi không thể kích hoạt nó, tôi đã thử cuộn theo các cách khác nhau, tôi đã thử thả cảm ứng khi cuộn ở vị trí cuối cùng và nó cũng được kích hoạt. Vì vậy, câu trả lời này là chính xác theo như tôi có thể kiểm tra nó.
fermmm

6

Các câu trả lời trên cho biết làm thế nào để có được vị trí bằng cách sử dụng các API khác nhau onScroll, onMomentumScrollEndv.v.; Nếu bạn muốn biết chỉ số trang, bạn có thể tính toán nó bằng cách sử dụng giá trị bù đắp.

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

nhập mô tả hình ảnh ở đây

Trong iOS, mối quan hệ giữa ScrollView và vùng hiển thị như sau: Hình ảnh đến từ https://www.objc.io/issues/3-views/scroll-view/

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


Nếu bạn muốn lấy "chỉ mục" của mục hiện tại, đây là câu trả lời chính xác. Bạn lấy event.nativeEvent.contentOffset.x / deviceWidth. Cảm ơn bạn đã giúp đỡ!
Sara Inés Calderón,

1

Đây là cách kết thúc việc hiển thị vị trí cuộn hiện tại từ chế độ xem. Tất cả những gì tôi đang làm là sử dụng trình nghe sự kiện trên cuộn và scrollEventThrottle. Sau đó, tôi chuyển nó như một sự kiện để xử lý chức năng cuộn mà tôi đã thực hiện và cập nhật các đạo cụ của mình.

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

cập nhật trạng thái trên onScroll có tốt không?
Hrishi

1

sử dụng onScroll vào vòng lặp vô hạn. onMomentumScrollEnd hoặc onScrollEndDrag có thể được sử dụng thay thế


0

Đối với trang, tôi đang làm việc trên một thành phần thứ tự cao hơn sử dụng cơ bản các phương pháp trên để thực hiện chính xác điều này. Nó thực sự chỉ mất một chút thời gian khi bạn đi đến những điểm tinh tế như những thay đổi về bố cục và nội dung ban đầu. Tôi sẽ không tuyên bố đã làm điều đó 'chính xác', nhưng theo một nghĩa nào đó, tôi sẽ xem xét câu trả lời chính xác để sử dụng thành phần thực hiện điều này một cách cẩn thận và nhất quán.

Xem: react-native-paged-scroll-view . Rất thích phản hồi, ngay cả khi tôi đã làm sai tất cả!


1
điều này thật tuyệt. Cảm ơn bạn đã làm điều này. tôi thấy điều này rất hữu ích ngay lập tức.
Marty Cortez

Rất vui mừng! Thực sự đánh giá cao các phản hồi!

1
Một lời xin lỗi -1, vì dự án công khai thừa nhận rằng nó không bị lỗi và thành phần có "một vài lỗi xấu xí" .
Mark Amery

Cảm ơn phản hồi @MarkAmery. :) Tìm kiếm thời gian cho mã nguồn mở không phải là dễ dàng.

-3

Tôi tin rằng contentOffsetsẽ cung cấp cho bạn một đối tượng chứa phần bù cuộn trên cùng bên trái:

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


4
Đây không phải là setter, không phải getter?
Aaron Wang,

1
-1 điều này là vô nghĩa; tài liệu mà bạn đã liên kết đến dành cho một phần mềm hỗ trợ JSX, không phải một thuộc tính JavaScript và chỉ có thể được sử dụng để đặt một phần bù chứ không phải để lấy một phần bù.
Mark Amery
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.