รับตำแหน่งเลื่อนปัจจุบันของ ScrollView ใน React Native


115

เป็นไปได้ไหมที่จะได้รับตำแหน่งการเลื่อนปัจจุบันหรือหน้าปัจจุบันของ<ScrollView>ส่วนประกอบใน React Native

สิ่งที่ชอบ:

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

เกี่ยวข้อง: ปัญหา GitHubแนะนำให้เพิ่มวิธีที่สะดวกในการรับข้อมูลนี้
Mark Amery

คำตอบ:


208

ลอง.

<ScrollView onScroll={this.handleScroll} />

แล้ว:

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

5
สิ่งนี้จะไม่เริ่มทำงานเสมอเมื่อค่าการเลื่อนเปลี่ยนไป หากตัวอย่างเช่น scrollview ถูกปรับขนาดทำให้ตอนนี้สามารถแสดงสิ่งทั้งหมดบนหน้าจอได้ในครั้งเดียวคุณดูเหมือนจะไม่ได้รับเหตุการณ์ onScroll ที่บอกคุณเสมอไป
Thomas Parslow

19
หรือevent.nativeEvent.contentOffset.xถ้าคุณมี ScrollView แนวนอน;) ขอบคุณ!
Tieme

2
นอกจากนี้เรายังสามารถใช้ใน Android
Janaka Pushpakumara

4
น่าผิดหวังเว้นแต่คุณscrollEventThrottleจะตั้งค่าเป็น 16 หรือต่ำกว่า (เพื่อให้ได้เหตุการณ์สำหรับทุกเฟรม) ไม่มีการรับประกันว่าสิ่งนี้จะรายงานการชดเชยในเฟรมสุดท้ายซึ่งหมายความว่าคุณต้องแน่ใจว่าคุณมีค่าชดเชยที่ถูกต้องหลังจากการเลื่อนเสร็จสิ้น คุณต้องตั้งค่าscrollEventThrottle={16}และยอมรับผลกระทบด้านประสิทธิภาพของสิ่งนั้น
Mark Amery

@bradoyler: เป็นไปได้หรือไม่ที่จะทราบค่าสูงสุดของevent.nativeEvent.contentOffset.y?
Isaac

55

คำเตือน: สิ่งต่อไปนี้เป็นผลมาจากการทดลองของฉันเองใน React Native 0.50 ScrollViewเอกสารปัจจุบันจะหายไปจำนวนมากข้อมูลที่ครอบคลุมด้านล่าง; ตัวอย่างเช่นonScrollEndDragไม่มีเอกสารทั้งหมด เนื่องจากทุกสิ่งที่นี่ขึ้นอยู่กับพฤติกรรมที่ไม่มีเอกสารเราจึงไม่สามารถให้คำมั่นสัญญาได้ว่าข้อมูลนี้จะยังคงถูกต้องในหนึ่งปีหรือแม้แต่หนึ่งเดือนนับจากนี้

นอกจากนี้ทุกอย่างดังต่อไปนี้ถือว่า scrollview แนวตั้งอย่างหมดจดมีYชดเชยเรามีความสนใจใน; การแปลเป็นx offsets หากจำเป็นหวังว่าจะเป็นแบบฝึกหัดที่ง่ายสำหรับผู้อ่าน


จัดการเหตุการณ์ต่างๆบนScrollViewใช้เวลาและปล่อยให้คุณได้รับการเลื่อนตำแหน่งในปัจจุบันผ่านevent event.nativeEvent.contentOffset.yเครื่องจัดการเหล่านี้บางตัวมีพฤติกรรมที่แตกต่างกันเล็กน้อยระหว่าง Android และ iOS ตามรายละเอียดด้านล่าง

onScroll

บน Android

เริ่มทำงานทุกเฟรมในขณะที่ผู้ใช้กำลังเลื่อนในทุกเฟรมในขณะที่มุมมองแบบเลื่อนกำลังเลื่อนหลังจากที่ผู้ใช้ปล่อยเฟรมในเฟรมสุดท้ายเมื่อมุมมองการเลื่อนหยุดลงและเมื่อใดก็ตามที่ออฟเซ็ตของมุมมองการเลื่อนเปลี่ยนไปอันเป็นผลมาจากเฟรม การเปลี่ยนแปลง (เช่นเนื่องจากการหมุนจากแนวนอนเป็นแนวตั้ง)

บน iOS

ไฟไหม้ขณะที่ผู้ใช้ลากหรือในขณะที่มุมมองเลื่อนจะร่อนที่ความถี่บางกำหนดโดยและที่มากที่สุดครั้งหนึ่งต่อกรอบเมื่อscrollEventThrottle หากผู้ใช้ออกจากมุมมองแบบเลื่อนในขณะที่มีโมเมนตัมเพียงพอที่จะเลื่อนตัวจัดการจะยิงเมื่อมันหยุดนิ่งหลังจากร่อน อย่างไรก็ตามหากผู้ใช้ลากแล้วปล่อยมุมมองการเลื่อนในขณะที่อยู่กับที่จะไม่รับประกันว่าจะยิงในตำแหน่งสุดท้ายเว้นแต่จะได้รับการตั้งค่าไว้เช่นนั้นซึ่งจะทำให้ทุกเฟรมของการเลื่อนเริ่มทำงานscrollEventThrottle={16}onScrollonScrollscrollEventThrottleonScroll

มีต้นทุนด้านประสิทธิภาพในการตั้งค่าscrollEventThrottle={16}ที่สามารถลดลงได้โดยการตั้งค่าเป็นจำนวนที่มากขึ้น อย่างไรก็ตามหมายความว่าonScrollจะไม่ยิงทุกเฟรม

onMomentumScrollEnd

เริ่มทำงานเมื่อมุมมองการเลื่อนหยุดลงหลังจากเลื่อน จะไม่เริ่มทำงานเลยหากผู้ใช้ปล่อยมุมมองแบบเลื่อนในขณะที่อยู่กับที่เพื่อไม่ให้เลื่อน

onScrollEndDrag

เริ่มทำงานเมื่อผู้ใช้หยุดลากมุมมองการเลื่อนไม่ว่ามุมมองการเลื่อนจะอยู่นิ่ง ๆ หรือเริ่มเลื่อน


เนื่องจากความแตกต่างในพฤติกรรมเหล่านี้วิธีที่ดีที่สุดในการติดตามการชดเชยขึ้นอยู่กับสถานการณ์ที่แน่นอนของคุณ ในกรณีที่ซับซ้อนที่สุด (คุณต้องรองรับ Android และ iOS รวมถึงการจัดการการเปลี่ยนแปลงในScrollViewเฟรมเนื่องจากการหมุนและคุณไม่ต้องการยอมรับการลงโทษประสิทธิภาพบน Android จากการตั้งค่าscrollEventThrottleเป็น 16) และคุณต้องจัดการ การเปลี่ยนแปลงเนื้อหาในมุมมองแบบเลื่อนด้วยแล้วมันก็เป็นระเบียบ

กรณีที่ง่ายที่สุดคือหากคุณต้องการจัดการกับ Android เท่านั้น เพียงใช้onScroll:

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

หากต้องการรองรับ iOS เพิ่มเติมหากคุณพอใจที่จะยิงonScrollตัวจัดการทุกเฟรมและยอมรับผลกระทบด้านประสิทธิภาพของสิ่งนั้นและหากคุณไม่จำเป็นต้องจัดการกับการเปลี่ยนแปลงเฟรมก็จะซับซ้อนขึ้นเล็กน้อย:

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

เพื่อลดค่าใช้จ่ายด้านประสิทธิภาพบน iOS ในขณะที่ยังคงรับประกันว่าเราบันทึกตำแหน่งใด ๆ ที่มุมมองเลื่อนกำหนดเราสามารถเพิ่มscrollEventThrottleและจัดหาonScrollEndDragตัวจัดการเพิ่มเติมได้:

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

แต่ถ้าเราต้องการจัดการการเปลี่ยนแปลงเฟรม (เช่นเนื่องจากเราอนุญาตให้หมุนอุปกรณ์เปลี่ยนความสูงที่มีอยู่สำหรับเฟรมของมุมมองการเลื่อน) และ / หรือการเปลี่ยนแปลงเนื้อหาเราจะต้องใช้ทั้งสองอย่างเพิ่มเติมonContentSizeChangeและonLayoutเพื่อติดตามความสูงของทั้งสองอย่าง เฟรมของมุมมองแบบเลื่อนและเนื้อหาและด้วยเหตุนี้จึงคำนวณค่าชดเชยสูงสุดที่เป็นไปได้อย่างต่อเนื่องและอนุมานเมื่อออฟเซ็ตถูกลดลงโดยอัตโนมัติเนื่องจากการเปลี่ยนแปลงขนาดเฟรมหรือเนื้อหา:

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

ใช่มันค่อนข้างน่ากลัว ฉันยังไม่ 100% บางอย่างที่มันก็จะทำงานที่ถูกต้องในกรณีที่คุณไปพร้อม ๆ กันเปลี่ยนขนาดของทั้งกรอบและเนื้อหาของมุมมองเลื่อน แต่เป็นสิ่งที่ดีที่สุดที่ฉันสามารถทำได้และจนกว่าคุณสมบัตินี้จะถูกเพิ่มเข้าไปในเฟรมเวิร์กฉันคิดว่านี่เป็นสิ่งที่ดีที่สุดที่ทุกคนสามารถทำได้


19

คำตอบของ Brad Oyler ถูกต้อง แต่คุณจะได้รับเพียงกิจกรรมเดียวเท่านั้น หากคุณต้องการรับการอัปเดตตำแหน่งการเลื่อนอย่างต่อเนื่องคุณควรตั้งscrollEventThrottleเสาดังนี้:

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

และตัวจัดการเหตุการณ์:

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

โปรดทราบว่าคุณอาจประสบปัญหาด้านประสิทธิภาพ ปรับคันเร่งให้เหมาะสม 16 ช่วยให้คุณได้รับการอัปเดตมากที่สุด 0 เท่านั้น


12
สิ่งนี้ไม่ถูกต้อง ประการแรก scrollEvenThrottle ใช้งานได้กับ iOS เท่านั้นไม่ใช่สำหรับ Android ประการที่สองจะควบคุมเฉพาะความถี่ที่คุณได้รับสาย onScroll ค่าเริ่มต้นคือหนึ่งครั้งต่อเฟรมไม่ใช่เหตุการณ์เดียว 1 ช่วยให้คุณอัปเดตมากขึ้นและ 16 ให้คุณน้อยลง 0 เป็นค่าเริ่มต้น คำตอบนี้ผิดอย่างสิ้นเชิง facebook.github.io/react-native/docs/…
Teodors

1
ฉันตอบสิ่งนี้ก่อนที่ Android จะรองรับโดย React Native ดังนั้นใช่คำตอบนี้ใช้ได้กับ iOS เท่านั้น ค่าเริ่มต้นคือศูนย์ซึ่งส่งผลให้เหตุการณ์เลื่อนถูกส่งเพียงครั้งเดียวในแต่ละครั้งที่เลื่อนมุมมอง
cutemachine

1
สัญกรณ์ es6 สำหรับฟังก์ชัน (event: Object) {} คืออะไร?
cbartondock

1
เพียงfunction(event) { }.
cutemachine

1
@ Teodors แม้ว่าคำตอบนี้จะไม่ครอบคลุมถึง Android แต่คำวิจารณ์ที่เหลือของคุณก็สับสนไปหมด scrollEventThrottle={16}ไม่ได้ให้การอัปเดต "น้อย" แก่คุณ ตัวเลขใด ๆ ในช่วง 1-16 จะให้อัตราการอัปเดตสูงสุดที่เป็นไปได้ ค่าเริ่มต้นของ 0 จะให้คุณ (บน iOS) เหตุการณ์หนึ่งเมื่อผู้ใช้เริ่มเลื่อนและไม่มีการอัปเดตในภายหลัง (ซึ่งค่อนข้างไร้ประโยชน์และอาจเป็นข้อบกพร่อง) ค่าเริ่มต้นไม่ใช่ "ครั้งเดียวต่อเฟรม" นอกจากข้อผิดพลาดสองข้อในความคิดเห็นของคุณแล้วคำยืนยันอื่น ๆ ของคุณไม่ขัดแย้งกับสิ่งที่คำตอบกล่าว (แม้ว่าคุณจะคิดว่าพวกเขาทำ)
Mark Amery

7

หากคุณต้องการเพียงแค่รับตำแหน่งปัจจุบัน (เช่นเมื่อกดปุ่มบางปุ่ม) แทนที่จะติดตามเมื่อใดก็ตามที่ผู้ใช้เลื่อนการเรียกonScrollผู้ฟังจะทำให้เกิดปัญหาด้านประสิทธิภาพ ปัจจุบันวิธีที่มีประสิทธิภาพมากที่สุดในการรับตำแหน่งการเลื่อนปัจจุบันคือการใช้แพ็คเกจreact-native-invoke มีตัวอย่างสำหรับสิ่งที่แน่นอนนี้ แต่แพคเกจทำสิ่งอื่นได้หลายอย่าง

อ่านเกี่ยวกับเรื่องนี้ได้ที่นี่ https://medium.com/@talkol/invoke-any-native-api-directly-from-pure-javascript-in-react-native-1fb6afcdf57d#.68ls1sopd


2
คุณรู้หรือไม่ว่าวิธีที่สะอาดกว่า (แฮ็กน้อยกว่าเล็กน้อย) ในการขอออฟเซ็ตมีอยู่ในขณะนี้หรือยังเป็นวิธีที่ดีที่สุดในการดำเนินการที่คุณทราบ (ฉันสงสัยว่าทำไมคำตอบนี้ไม่ได้รับการโหวตเพราะเป็นคำตอบเดียวที่ดูเข้าท่า)
cglacet

1
ไม่มีพัสดุอยู่ที่นั่นแล้วมันย้ายไปที่ไหนหรือเปล่า? Github กำลังแสดง 404
Noitidart

6

หากต้องการรับ x / y หลังจากการเลื่อนสิ้นสุดลงตามที่มีการร้องขอคำถามเดิมวิธีที่ง่ายที่สุดอาจเป็นดังนี้:

<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; onMomentumScrollEndจะถูกทริกเกอร์ก็ต่อเมื่อมุมมองแบบเลื่อนมีโมเมนตัมบางอย่างเมื่อผู้ใช้ปล่อยไป หากปล่อยในขณะที่มุมมองเลื่อนไม่เคลื่อนไหวคุณจะไม่ได้รับเหตุการณ์โมเมนตัมใด ๆ
Mark Amery

1
ฉันทดสอบ onMomentumScrollEnd และมันเป็นไปไม่ได้สำหรับฉันที่จะไม่เรียกมันฉันลองเลื่อนด้วยวิธีต่างๆฉันลองปล่อยการสัมผัสเมื่อเลื่อนอยู่ในตำแหน่งสุดท้ายและมันก็ถูกทริกเกอร์ด้วย คำตอบนี้ถูกต้องเท่าที่ฉันทดสอบได้
fermmm

6

คำตอบข้างต้นบอกว่าจะได้รับตำแหน่งโดยใช้ API ที่แตกต่างกันonScroll, onMomentumScrollEndฯลฯ ; หากคุณต้องการทราบดัชนีเพจคุณสามารถคำนวณได้โดยใช้ค่าออฟเซ็ต

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

ใส่คำอธิบายภาพที่นี่

ใน iOS ความสัมพันธ์ระหว่าง ScrollView และพื้นที่ที่มองเห็นได้มีดังต่อไปนี้: ภาพมาจาก https://www.objc.io/issues/3-views/scroll-view/

อ้างอิง: https://www.objc.io/issues/3-views/scroll-view/


หากคุณต้องการได้รับ "ดัชนี" ของรายการปัจจุบันนี่คือคำตอบที่ถูกต้อง คุณใช้ event.nativeEvent.contentOffset.x / deviceWidth ขอบคุณสำหรับความช่วยเหลือของคุณ!
Sara

1

นี่คือวิธีที่ทำให้ตำแหน่งการเลื่อนปัจจุบันปรากฏขึ้นจากมุมมอง สิ่งที่ฉันทำคือใช้ on scroll event listener และ scrollEventThrottle จากนั้นฉันจะส่งต่อเป็นเหตุการณ์เพื่อจัดการฟังก์ชั่นเลื่อนที่ฉันสร้างและอัปเดตอุปกรณ์ประกอบฉาก

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

การอัปเดตสถานะบน onScroll เป็นการดีหรือไม่
Hrishi

1

การใช้ onScroll เข้าสู่การวนซ้ำแบบไม่มีที่สิ้นสุด onMomentumScrollEnd หรือ onScrollEndDrag แทนได้


0

สำหรับหน้านี้ฉันกำลังทำงานกับส่วนประกอบลำดับที่สูงขึ้นซึ่งใช้วิธีการข้างต้นเพื่อทำสิ่งนี้โดยทั่วไป จริงๆแล้วใช้เวลาเพียงเล็กน้อยเมื่อคุณลงลึกถึงรายละเอียดปลีกย่อยเช่นการจัดวางเริ่มต้นและการเปลี่ยนแปลงเนื้อหา ฉันจะไม่อ้างว่าทำ 'ถูกต้อง' แต่ในบางแง่ฉันจะพิจารณาคำตอบที่ถูกต้องเพื่อใช้ส่วนประกอบที่ทำสิ่งนี้อย่างรอบคอบและสม่ำเสมอ

ดู: ตอบสนองพื้นเมืองเพจเลื่อนมุมมอง ชอบคำติชมแม้ว่าฉันจะทำผิดทั้งหมดก็ตาม!


1
นี่มันเจ๋งมาก. ขอบคุณที่ทำสิ่งนี้ ฉันพบว่าสิ่งนี้มีประโยชน์มากทันที
Marty Cortez

ดีใจมาก! ขอบคุณจริงๆสำหรับคำติชม!

1
ขอโทษ -1 เนื่องจากโครงการเปิดเผยยอมรับว่ามันเป็น unmaintained และว่าองค์ประกอบที่มี"สองสามข้อบกพร่องที่น่าเกลียด"
Mark Amery

ขอบคุณสำหรับคำติชม @MarkAmery :) การหาเวลาสำหรับโอเพ่นซอร์สไม่ใช่เรื่องง่าย

-3

ฉันเชื่อว่าcontentOffsetจะให้วัตถุที่มีออฟเซ็ตการเลื่อนด้านซ้ายบน:

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


4
นี่ไม่ใช่ setter ไม่ใช่ getter?
Aaron Wang

1
-1 นี่เป็นเรื่องไร้สาระ เอกสารที่คุณเชื่อมโยงมีไว้สำหรับ JSX prop ไม่ใช่คุณสมบัติ JavaScript และสามารถใช้เพื่อตั้งค่าออฟเซ็ตเท่านั้นไม่ใช่เพื่อรับ
Mark Amery
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.