Obtener la posición de desplazamiento actual de ScrollView en React Native

115

¿Es posible obtener la posición de desplazamiento actual o la página actual de un <ScrollView>componente en React Native?

Entonces algo como:

<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>
Cantó
fuente
Relevante: un problema de GitHub que sugiere agregar una forma conveniente de obtener esta información.
Mark Amery

Respuestas:

207

Tratar.

<ScrollView onScroll={this.handleScroll} />

Y entonces:

handleScroll: function(event: Object) {
 console.log(event.nativeEvent.contentOffset.y);
},
brad oyler
fuente
5
Esto no siempre se activará cuando cambie el valor de desplazamiento. Si, por ejemplo, se cambia el tamaño de la vista de desplazamiento, lo que hace que ahora pueda mostrar todo en la pantalla a la vez, no siempre parece que aparezca un evento onScroll que se lo diga.
Thomas Parslow
19
O event.nativeEvent.contentOffset.xsi tiene un ScrollView horizontal;) ¡Gracias!
Tieme
2
Esto también lo podemos usar en Android.
Janaka Pushpakumara
4
Es frustrante que, a menos que establezca scrollEventThrottle16 o menos (para obtener un evento para cada fotograma), no hay garantía de que esto informe el desplazamiento en el fotograma final , lo que significa que para asegurarse de que tiene el desplazamiento correcto después de que el desplazamiento haya terminado, necesita establecer scrollEventThrottle={16}y aceptar el impacto en el rendimiento de eso.
Mark Amery
@bradoyler: ¿es posible conocer el valor máximo de event.nativeEvent.contentOffset.y?
Isaac
55

Descargo de responsabilidad: lo que sigue es principalmente el resultado de mi propia experimentación en React Native 0.50. Actualmente, a la ScrollViewdocumentación le falta mucha de la información que se describe a continuación; por ejemplo, onScrollEndDragestá completamente indocumentado. Dado que todo aquí se basa en un comportamiento indocumentado, desafortunadamente no puedo prometer que esta información seguirá siendo correcta dentro de un año o incluso un mes.

Además, todo lo que se muestra a continuación asume una vista de desplazamiento puramente vertical cuyo desplazamiento y nos interesa; traducir ax compensaciones, si es necesario, es con suerte un ejercicio fácil para el lector.


Varios controladores de eventos en una ScrollViewtoma eventy le permiten obtener la posición de desplazamiento actual a través de event.nativeEvent.contentOffset.y. Algunos de estos controladores tienen un comportamiento ligeramente diferente entre Android e iOS, como se detalla a continuación.

onScroll

En Android

Dispara cada fotograma mientras el usuario se desplaza, en cada fotograma mientras la vista de desplazamiento se desliza después de que el usuario la suelta, en el fotograma final cuando la vista de desplazamiento se detiene, y también siempre que el desplazamiento de la vista de desplazamiento cambia como resultado de su fotograma cambiando (por ejemplo, debido a la rotación de paisaje a retrato).

En iOS

Se dispara mientras el usuario arrastra o mientras la vista de desplazamiento se desliza, a una frecuencia determinada por scrollEventThrottley como máximo una vez por cuadro cuando scrollEventThrottle={16}. Si el usuario suelta la vista de desplazamiento mientras tiene suficiente impulso para planear, el onScrollcontrolador también disparará cuando se detenga después de planear. Sin embargo, si el usuario arrastra y suelta el punto de vista de desplazamiento mientras está parado, onScrollestá no garantizada al fuego para la posición final a menos que scrollEventThrottlese ha establecido de tal manera que onScrolllos incendios cada fotograma de desplazamiento.

Hay un costo de rendimiento en la configuración scrollEventThrottle={16}que se puede reducir configurándolo en un número mayor. Sin embargo, esto significa que onScrollno disparará todos los fotogramas.

onMomentumScrollEnd

Se dispara cuando la vista de desplazamiento se detiene después de planear. No dispara en absoluto si el usuario suelta la vista de desplazamiento mientras está estacionaria para que no se deslice.

onScrollEndDrag

Se dispara cuando el usuario deja de arrastrar la vista de desplazamiento, independientemente de si la vista de desplazamiento se deja estacionaria o comienza a deslizarse.


Dadas estas diferencias de comportamiento, la mejor manera de realizar un seguimiento de la compensación depende de sus circunstancias precisas. En el caso más complicado (necesita ser compatible con Android e iOS, incluido el manejo de cambios en el ScrollViewmarco debido a la rotación, y no desea aceptar la penalización de rendimiento en Android de la configuración scrollEventThrottlea 16), y debe manejar También cambia el contenido en la vista de desplazamiento, entonces es un maldito desastre.

El caso más simple es si solo necesita manejar Android; solo usa onScroll:

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

Para admitir adicionalmente iOS, si está contento de despedir al onScrollcontrolador en cada cuadro y acepta las implicaciones de rendimiento de eso, y si no necesita manejar los cambios de cuadro, entonces es solo un poco más complicado:

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

Para reducir la sobrecarga de rendimiento en iOS y, al mismo tiempo, garantizar que registremos cualquier posición en la que se asiente la vista de desplazamiento, podemos aumentar scrollEventThrottley, además, proporcionar un onScrollEndDragcontrolador:

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

Pero si queremos manejar cambios de marco (por ejemplo, porque permitimos que se gire el dispositivo, cambiando la altura disponible para el marco de la vista de desplazamiento) y / o cambios de contenido, entonces debemos implementar adicionalmente tanto onContentSizeChangeyonLayout realizar un seguimiento de la altura de ambos el marco de la vista de desplazamiento y su contenido y, por lo tanto, calcular continuamente el desplazamiento máximo posible e inferir cuándo el desplazamiento se ha reducido automáticamente debido a un cambio de tamaño del marco o del contenido:

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

Sí, es bastante horrible. Tampoco estoy 100% seguro de que siempre funcione bien en los casos en los que cambie simultáneamente el tamaño del marco y el contenido de la vista de desplazamiento. Pero es lo mejor que se me ocurre, y hasta que esta característica se agregue dentro del marco en sí , creo que es lo mejor que cualquiera puede hacer.

Mark Amery
fuente
19

La respuesta de Brad Oyler es correcta. Pero solo recibirás un evento. Si necesita obtener actualizaciones constantes de la posición de desplazamiento, debe configurar el scrollEventThrottleaccesorio, así:

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

Y el controlador de eventos:

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

Tenga en cuenta que puede tener problemas de rendimiento. Ajuste el acelerador en consecuencia. 16 te ofrece la mayor cantidad de actualizaciones. 0 solo uno.

linda maquina
fuente
12
Esto es incorrecto. Por un lado, scrollEvenThrottle solo funciona para iOS y no para Android. En segundo lugar, solo regula la frecuencia con la que recibe llamadas onScroll. El valor predeterminado es una vez por fotograma, no un evento. 1 le brinda más actualizaciones y 16 le brinda menos. 0 es el predeterminado. Esta respuesta es completamente incorrecta. facebook.github.io/react-native/docs/…
Teodors
1
Respondí esto antes de que Android fuera compatible con React Native, así que sí, la respuesta se aplica solo a iOS. El valor predeterminado es cero, lo que hace que el evento de desplazamiento se envíe solo una vez cada vez que se desplaza la vista.
cutemachine
1
¿Cuál sería la notación es6 para la función (evento: Objeto) {}?
cbartondock
1
Solo function(event) { }.
cutemachine
1
@Teodors Si bien esta respuesta no cubre Android, el resto de sus críticas es completamente confuso. scrollEventThrottle={16}no le da "menos" actualizaciones; cualquier número en el rango del 1 al 16 le brinda la máxima tasa de actualizaciones posible. El valor predeterminado de 0 le brinda (en iOS) un evento cuando el usuario comienza a desplazarse y luego no hay actualizaciones posteriores (lo cual es bastante inútil y posiblemente un error); el valor predeterminado no es "una vez por cuadro". Además de esos dos errores en su comentario, sus otras afirmaciones no contradicen nada de lo que dice la respuesta (aunque parece pensar que lo hacen).
Mark Amery
7

Si simplemente desea obtener la posición actual (por ejemplo, cuando se presiona algún botón) en lugar de rastrearlo cada vez que el usuario se desplaza, entonces invocar a un onScrolloyente causará problemas de rendimiento. Actualmente, la forma más eficaz de simplemente obtener la posición de desplazamiento actual es usar react-native-invoke paquete . Hay un ejemplo para esto exactamente, pero el paquete hace muchas otras cosas.

Lea sobre esto aquí. https://medium.com/@talkol/invoke-any-native-api-directly-from-pure-javascript-in-react-native-1fb6afcdf57d#.68ls1sopd

Teodors
fuente
2
¿Sabe si ahora existe una forma más limpia (un poco menos hacky) de solicitar compensación, o sigue siendo esta la mejor forma de hacerlo que conoce? (Me pregunto por qué esta respuesta no se vota a favor, ya que es la única que parece tener sentido)
cglacet
1
El paquete ya no está allí, ¿se mudó a alguna parte? Github muestra 404.
Noitidart
6

Para obtener el x / y después de que el desplazamiento terminó como lo solicitaban las preguntas originales, la forma más fácil es probablemente esta:

<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
fuente
-1; onMomentumScrollEndsolo se activa si la vista de desplazamiento tiene cierto impulso cuando el usuario la suelta. Si se sueltan mientras la vista de desplazamiento no se está moviendo, nunca obtendrá ninguno de los eventos de impulso.
Mark Amery
1
Probé en MomentumScrollEnd y me fue imposible no activarlo, intenté desplazarme de diferentes maneras, intenté soltar el toque cuando el scroll estaba en la posición final y también se activó. Entonces, esta respuesta es correcta en la medida de lo que pude probar.
fermmm
6

Las respuestas anteriores indican cómo obtener la posición utilizando diferentes API onScroll, onMomentumScrollEndetc. Si desea conocer el índice de la página, puede calcularlo utilizando el valor de compensación.

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

ingrese la descripción de la imagen aquí

En iOS, la relación entre ScrollView y la región visible es la siguiente: La imagen proviene de https://www.objc.io/issues/3-views/scroll-view/

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

RY Zheng
fuente
Si desea obtener el "índice" del elemento actual, esta es la respuesta correcta. Toma event.nativeEvent.contentOffset.x / deviceWidth. ¡Gracias por tu ayuda!
Sara Inés Calderón
1

Así es como terminó obteniendo la posición de desplazamiento actual en vivo desde la vista. Todo lo que estoy haciendo es usar el detector de eventos de desplazamiento y scrollEventThrottle. Luego lo paso como un evento para manejar la función de desplazamiento que hice y actualizar mis accesorios.

 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
fuente
¿Es bueno actualizar el estado de onScroll considerando el rendimiento?
Hrishi
1

el uso de onScroll entra en un bucle infinito. En su lugar, se puede utilizar onMomentumScrollEnd o onScrollEndDrag

kmehmet
fuente
0

En cuanto a la página, estoy trabajando en un componente de orden superior que utiliza básicamente los métodos anteriores para hacer exactamente esto. En realidad, solo lleva un poco de tiempo llegar a las sutilezas como el diseño inicial y los cambios de contenido. No afirmaré haberlo hecho 'correctamente', pero en cierto sentido consideraría la respuesta correcta para usar un componente que lo haga de manera cuidadosa y consistente.

Ver: react-native-paged-scroll-view . Me encantaría recibir comentarios, ¡incluso si lo he hecho todo mal!


fuente
1
esto es asombroso. gracias por hacer esto. Encontré esto muy útil de inmediato.
Marty Cortez
¡Tan contento! ¡Realmente aprecio los comentarios!
1
Un -1 de disculpa, ya que el proyecto admite abiertamente que no se ha mantenido y que el componente tiene "un par de errores feos" .
Mark Amery
Gracias por los comentarios @MarkAmery. :) Encontrar el tiempo para el código abierto no es fácil.
-3

Yo creo contentOffset le dará un objeto que contiene el desplazamiento de desplazamiento superior izquierdo:

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

Colin Ramsay
fuente
4
¿No es esto un setter, no un getter?
Aaron Wang
1
-1 esto es una tontería; la documentación a la que ha vinculado es para un accesorio JSX, no una propiedad de JavaScript, y solo se puede usar para establecer un desplazamiento, no para obtener uno.
Mark Amery