Получить текущую позицию прокрутки 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
Это не всегда срабатывает при изменении значения прокрутки. Если, например, размер scrollview изменен, в результате чего теперь он может отображать все на экране сразу, вы, похоже, не всегда получаете сообщение о событии onScroll.
Томас Парслоу,

19
Или, event.nativeEvent.contentOffset.xесли у вас есть горизонтальный ScrollView;) Спасибо!
Tieme

2
Это также мы можем использовать в Android.
Джанака Пушпакумара

4
К сожалению, если вы не установите scrollEventThrottleзначение 16 или ниже (чтобы получать событие для каждого кадра), нет никакой гарантии, что это будет сообщать смещение в последнем кадре - это означает, что для уверенности в правильном смещении после завершения прокрутки, вам необходимо установить scrollEventThrottle={16}и принять влияние этого на производительность.
Марк Эмери

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

55

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

Кроме того, все, что ниже, предполагает чисто вертикальную прокрутку, чье смещение по y нас интересует; перевод в 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/…
Teodors

1
Я ответил на это до того, как Android был поддержан React Native, так что да, ответ относится только к iOS. Значение по умолчанию - ноль, что приводит к тому, что событие прокрутки отправляется только один раз при каждой прокрутке представления.
cutemachine

1
Каким будет обозначение функции (событие: объект) {} в es6?
cbartondock

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

1
@Teodors Хотя этот ответ не касается Android, остальная часть вашей критики полностью запуталась. scrollEventThrottle={16}это не даст вам «меньше» обновления; любое число в диапазоне 1–16 дает максимально возможную скорость обновления. Значение по умолчанию 0 дает вам (в iOS) одно событие, когда пользователь начинает прокрутку, а затем никаких последующих обновлений (что довольно бесполезно и, возможно, является ошибкой); по умолчанию это не «один раз за кадр». Помимо этих двух ошибок в вашем комментарии, другие ваши утверждения не противоречат тому, что говорится в ответе (хотя вы, кажется, думаете, что они противоречат).
Марк Эмери

7

Если вы хотите просто получить текущую позицию (например, при нажатии какой-либо кнопки), а не отслеживать ее всякий раз, когда пользователь прокручивает, то вызов onScrollслушателя вызовет проблемы с производительностью. В настоящее время наиболее эффективный способ просто получить текущую позицию прокрутки - использовать пакет response-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запускается только в том случае, если просмотр прокрутки имеет некоторый импульс, когда пользователь отпускает его. Если они отпустят, пока ползунок не движется, вы никогда не получите ни одного события импульса.
Марк Эмери

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. Спасибо за вашу помощь!
Сара Инес Кальдерон

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 с учетом производительности?
Hrishi

1

использование onScroll входит в бесконечный цикл. Вместо этого можно использовать onMomentumScrollEnd или onScrollEndDrag


0

Что касается страницы, я работаю над компонентом более высокого порядка, который использует в основном вышеуказанные методы для выполнения именно этого. На самом деле это займет совсем немного времени, если вы перейдете к таким тонкостям, как первоначальный макет и изменение содержимого. Я не буду утверждать, что сделал это «правильно», но в некотором смысле я бы посчитал правильным ответом использовать компонент, который делает это осторожно и последовательно.

Смотрите: response-native-paged-scroll-view . Хотел бы получить обратную связь, даже если я все сделал неправильно!


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.