Отримайте поточну позицію прокрутки 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 пропонує додавання зручного способу отримання цієї інформації.
Марк Амерді

Відповіді:


208

Спробуйте.

<ScrollView onScroll={this.handleScroll} />

І потім:

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

5
Це не завжди спрацьовує, коли значення прокрутки змінюється. Якщо, наприклад, перегляд прокрутки змінено, завдяки чому тепер він зможе одразу відображати всю річ на екрані, вам не завжди здається, що вам повідомить подію onScroll.
Thomas Parslow

19
Або event.nativeEvent.contentOffset.xякщо у вас горизонтальний ScrollView;) Дякую!
Тієм

2
Це також ми можемо використовувати в Android.
Янака Пушпакумара

4
Розчаровуючи, якщо ви не встановите scrollEventThrottle16 або менше (щоб отримати подію для кожного окремого кадру), немає гарантії, що це повідомить про зміщення на остаточному кадрі - це означає, що ви впевнені, що у вас є правильне зміщення після закінчення прокрутки, вам потрібно встановити scrollEventThrottle={16}та прийняти вплив на ефективність цього.
Марк Амерді

@bradoyler: чи можна знати максимальне значення event.nativeEvent.contentOffset.y?
Ісаак

55

Відмова від відповідальності: наступне - це насамперед результат мого власного експерименту в React Native 0.50. Наразі в ScrollViewдокументації відсутня багато інформації, яка висвітлюється нижче; наприклад onScrollEndDrag, повністю недокументований. Оскільки тут все покладається на незадокументовану поведінку, я, на жаль, не обіцяю, що ця інформація залишатиметься правильною через рік чи навіть місяць.

Крім того , всі , що нижче передбачає чисто вертикальний Scrollview якого у компенсовано нас цікавить; перекладаємо на x компенсації, якщо потрібно, сподіваємось, легка вправа для читача.


Різні обробники подій під час ScrollViewприйому eventта дозволяють отримати поточну позицію прокрутки через event.nativeEvent.contentOffset.y. Деякі з цих обробників мають дещо іншу поведінку між Android та iOS, як детально описано нижче.

onScroll

На Android

Створює кожен кадр, коли користувач прокручує, на кожному кадрі, коли подання прокрутки ковзає після того, як користувач відпустить його, на остаточному кадрі, коли огляд прокрутки перебуває в спокої, а також коли зміщення погляду прокрутки змінюється в результаті його кадру змінюється (наприклад, через обертання від пейзажу до портрета).

На iOS

Створюється під час перетягування користувачем або під час перегляду прокрутки, з деякою частотою, що визначається scrollEventThrottleі, максимум, один раз на кадр, коли scrollEventThrottle={16}. Якщо користувач випустить подання прокрутки, поки у нього є достатній імпульс для ковзання, onScrollобробник також вистрілить, коли стане спокою після плавання. Однак, якщо користувач перетягує , а потім відпускає вид прокрутки , поки вона знаходиться в нерухомому стані , onScrollце НЕ гарантує вогню для кінцевої позиції , якщо scrollEventThrottleне було встановлено таким чином, щоonScroll пожежі кожен кадр прокрутки.

Існує вартість продуктивності налаштування, 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

Відповідь Бреда Ойлера правильна. Але ви отримаєте лише одну подію. Якщо вам потрібно постійно оновлювати положення прокрутки, слід встановити 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/…
Теодори

1
Я відповів на це ще до того, як Android підтримав React Native, так що так, відповідь стосується лише iOS. Значення за замовчуванням дорівнює нулю, що призводить до того, що подія прокрутки надсилається лише один раз під час прокрутки подання.
cutemachine

1
Яким буде позначення es6 для функції (подія: Об'єкт) {}?
cbartondock

1
Просто function(event) { }.
cutemachine

1
@Teodors Хоча ця відповідь не стосується Android, решта вашої критики повністю заплутана. scrollEventThrottle={16}це НЕ дасть вам «менше» оновлення; будь-яке число в діапазоні 1-16 дає максимально можливу швидкість оновлень. За замовчуванням 0 дає вам (на iOS) одну подію, коли користувач починає прокручувати, а потім не здійснювати наступних оновлень (що є досить марним і, можливо, помилкою); за замовчуванням не "раз на кадр". Окрім цих двох помилок у вашому коментарі, ваші інші твердження не суперечать нічому, що говорить відповідь (хоча ви, здається, думаєте, що вони роблять).
Марк Амері

7

Якщо ви хочете просто отримати поточну позицію (наприклад, коли натиснути якусь кнопку), а не відстежувати її кожного разу, коли користувач прокручує, то виклик onScrollслухача спричинить проблеми з продуктивністю. В даний час найефективнішим способом просто отримати поточну позицію прокрутки є використання пакета react-native-invoke . Для цієї точної речі є приклад, але пакет робить багато інших речей.

Про це читайте тут. https://medium.com/@talkol/invoke-any-native-api-direct-from-pure-javascript-in-react-native-1fb6afcdf57d#.68ls1sopd


2
Чи знаєте ви, чи існує більш чистий спосіб (трохи менше хакіт) вимагати компенсації, чи це все-таки найкращий спосіб зробити це вам відомо? (Цікаво, чому ця відповідь не оголошена, оскільки вона є єдиною, яка має сенс)
cglacet

1
Пакету вже немає, чи він кудись перемістився? Github показує 404.
Noitidart

6

Щоб отримати х / у після закінчення прокрутки, як запитували оригінальні запитання, найпростіший спосіб, мабуть, такий:

<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спрацьовує лише в тому випадку, якщо вигляд прокрутки має деякий імпульс, коли користувач відпускає його. Якщо вони відпускаються, поки подання прокрутки не рухається, ви ніколи не отримаєте жодної події імпульсу.
Марк Амері

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/isissue/3-views/scroll-view/

посилання: https://www.objc.io/isissue/3-views/scroll-view/


Якщо ви хочете отримати "індекс" поточного елемента, це правильна відповідь. Ви приймаєте event.nativeEvent.contentOffset.x / deviceWidth. Спасибі за вашу допомогу!
Сара Інес Кальдерон

1

Ось так отримано поточне положення прокрутки в прямому ефірі з подання. Все, що я роблю, - це використання слухача подій прокрутки та 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 з огляду на продуктивність?
Хріші

1

використання OnScroll входить в нескінченний цикл. Натомість можна використовувати onMomentumScrollEnd або onScrollEndDrag


0

Щодо сторінки, я працюю над компонентом вищого порядку, який використовує в основному вищезазначені методи, щоб зробити саме це. Насправді потрібно трохи часу, коли ви переходите до тонкощів, таких як початковий макет і зміст вмісту. Я не буду стверджувати, що зробив це "правильно", але в деякому сенсі я вважав би правильною відповіддю використовувати компонент, який робить це ретельно і послідовно.

Див.: Реакція-натискання-прокрутка-перегляд-перегляд . Буду любити відгук, навіть якщо це все, що я все зробив неправильно!


1
Це круто. дякую, що зробили це. я знайшов це дуже корисним відразу.
Марті Кортес

Такий радий! Дійсно оцініть відгуки!

1
Вибачте -1, оскільки проект відкрито визнає, що він недоторканий і що компонент має "кілька негарних помилок" .
Марк Амерді

Дякуємо за відгук @MarkAmery. :) Знайти час для відкритого коду непросто.

-3

я вірю contentOffset , що ви отримаєте об'єкт, що містить верхнє ліве зміщення прокрутки:

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


4
Це не сеттер, не геттер?
Аарон Ван

1
-1 це нісенітниця; документація, з якою ви пов’язані, стосується супроводу JSX, а не властивості JavaScript, і може використовуватися лише для встановлення зміщення, а не для отримання.
Марк Амері
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.