Obtenha a posição de rolagem atual de ScrollView no React Native

115

É possível obter a posição de rolagem atual ou a página atual de um <ScrollView>componente no React Native?

Então, 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>
Cantou
fonte
Relevante: um problema do GitHub que sugere adicionar uma maneira conveniente de obter essas informações.
Mark Amery

Respostas:

208

Experimentar.

<ScrollView onScroll={this.handleScroll} />

E depois:

handleScroll: function(event: Object) {
 console.log(event.nativeEvent.contentOffset.y);
},
Brad Oyler
fonte
5
Isso nem sempre será acionado quando o valor de rolagem mudar. Se, por exemplo, scrollview é redimensionado fazendo com que agora seja capaz de exibir tudo na tela de uma vez, você nem sempre parece receber um evento onScroll informando você.
Thomas Parslow
19
Ou event.nativeEvent.contentOffset.xse você tiver um ScrollView horizontal;) Obrigado!
Tieme
2
Isso também podemos usar no Android.
Janaka Pushpakumara
4
Frustrante, a menos que você defina scrollEventThrottlecomo 16 ou menos (para obter um evento para cada quadro), não há garantia de que isso relatará o deslocamento no quadro final - o que significa que, para ter certeza de que você tem o deslocamento correto após o término da rolagem, você precisa definir scrollEventThrottle={16}e aceitar o impacto disso no desempenho.
Mark Amery
@bradoyler: é possível saber o valor máximo de event.nativeEvent.contentOffset.y?
Isaac
55

Isenção de responsabilidade: o que se segue é principalmente o resultado de minhas próprias experiências no React Native 0.50. A ScrollViewdocumentação está faltando muitas das informações cobertas abaixo; por exemplo, onScrollEndDragé completamente indocumentado. Como tudo aqui depende de comportamento não documentado, infelizmente não posso prometer que essa informação permanecerá correta daqui a um ano ou até um mês.

Além disso, tudo a seguir assume uma visualização de rolagem puramente vertical, em cujo deslocamento y estamos interessados; traduzir para x offsets, se necessário, é um exercício fácil para o leitor.


Vários manipuladores de eventos em um ScrollViewassumir um evente deixá-lo começar a posição de rolagem atual via event.nativeEvent.contentOffset.y. Alguns desses manipuladores têm um comportamento ligeiramente diferente entre Android e iOS, conforme detalhado abaixo.

onScroll

No Android

Dispara cada quadro enquanto o usuário está rolando, em cada quadro enquanto a visualização de rolagem está deslizando depois que o usuário a libera, no quadro final quando a visualização de rolagem pára e também sempre que o deslocamento da visualização de rolagem muda como resultado de seu quadro mudança (por exemplo, devido à rotação de paisagem para retrato).

No iOS

Dispara enquanto o usuário está arrastando ou enquanto a visualização de rolagem está deslizando, em alguma frequência determinada por scrollEventThrottlee no máximo uma vez por quadro quando scrollEventThrottle={16}. Se o usuário liberar a visualização de rolagem enquanto ela tem impulso suficiente para deslizar, o onScrollmanipulador também disparará quando chegar ao repouso após deslizar. No entanto, se o usuário arrasta e, em seguida, libera a exibição de rolagem enquanto ele está parado, onScrollé não garantido para o fogo para a posição final, a menos que scrollEventThrottletenha sido definida de tal forma que onScrollincêndios cada quadro de rolagem.

Há um custo de desempenho na configuração scrollEventThrottle={16}que pode ser reduzido configurando-o para um número maior. No entanto, isso significa que onScrollnão disparará todos os quadros.

onMomentumScrollEnd

Dispara quando a visualização de rolagem para após deslizar. Não dispara se o usuário liberar a visualização de rolagem enquanto ela estiver parada de forma que não deslize.

onScrollEndDrag

Dispara quando o usuário para de arrastar a visualização de rolagem - independentemente de a visualização de rolagem ficar parada ou começar a deslizar.


Dadas essas diferenças de comportamento, a melhor maneira de controlar o deslocamento depende de suas circunstâncias precisas. No caso mais complicado (você precisa suportar Android e iOS, incluindo manipulação de mudanças no ScrollViewquadro de devido à rotação, e você não quer aceitar a penalidade de desempenho no Android da configuração scrollEventThrottlepara 16), e você precisa lidar com muda para o conteúdo na visualização de rolagem também, então é uma bagunça certa.

O caso mais simples é se você só precisa lidar com o Android; apenas use onScroll:

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

Para oferecer suporte adicional ao iOS, se você estiver feliz em disparar o onScrollmanipulador em todos os quadros e aceitar as implicações de desempenho disso, e se não precisar lidar com mudanças de quadro, então é apenas um pouco mais complicado:

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

Para reduzir a sobrecarga de desempenho no iOS e, ao mesmo tempo, garantir que registremos qualquer posição em que a visualização de rolagem se estabelece, podemos aumentar scrollEventThrottlee fornecer adicionalmente um onScrollEndDragmanipulador:

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

Mas se quisermos lidar com mudanças de quadro (por exemplo, porque permitimos que o dispositivo seja girado, mudando a altura disponível para o quadro da visualização de rolagem) e / ou mudanças de conteúdo, devemos implementar adicionalmente ambos onContentSizeChangee onLayoutmanter o controle da altura de ambos o quadro da visualização de rolagem e seu conteúdo e, assim, calcular continuamente o deslocamento máximo possível e inferir quando o deslocamento foi reduzido automaticamente devido a uma alteração no tamanho do quadro ou do conteúdo:

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

Sim, é horrível. Também não estou 100% certo de que sempre funcionará bem nos casos em que você alterar simultaneamente o tamanho do quadro e o conteúdo da visualização de rolagem. Mas é o melhor que posso inventar e, até que esse recurso seja adicionado ao próprio framework , acho que é o melhor que alguém pode fazer.

Mark Amery
fonte
19

A resposta de Brad Oyler está correta. Mas você receberá apenas um evento. Se você precisa obter atualizações constantes da posição de rolagem, você deve definir o scrollEventThrottlesuporte, assim:

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

E o manipulador de eventos:

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

Esteja ciente de que você pode ter problemas de desempenho. Ajuste o acelerador de acordo. 16 oferece mais atualizações. 0 apenas um.

máquina fofa
fonte
12
Isso está incorreto. Por um lado, scrollEvenThrottle funciona apenas para iOS e não para Android. Em segundo lugar, ele apenas regula a frequência com que você recebe chamadas onScroll. O padrão é uma vez por quadro, não um evento. 1 oferece mais atualizações e 16 menos. 0 é o padrão. Esta resposta está completamente errada. facebook.github.io/react-native/docs/…
Teodors
1
Eu respondi isso antes que o Android fosse compatível com React Native, então sim, a resposta se aplica apenas ao iOS. O valor padrão é zero, o que resulta no evento de rolagem sendo enviado apenas uma vez a cada vez que a exibição é rolada.
cutemachine
1
Qual seria a notação es6 para função (evento: Objeto) {}?
cbartondock
1
Apenas function(event) { }.
cutemachine
1
@Teodors Embora essa resposta não cubra o Android, o resto de sua crítica é completamente confusa. scrollEventThrottle={16}se não lhe dar "menos" atualizações; qualquer número no intervalo de 1 a 16 fornece a taxa máxima possível de atualizações. O padrão de 0 oferece a você (no iOS) um evento quando o usuário começa a rolar a tela e nenhuma atualização subsequente (o que é bastante inútil e possivelmente um bug); o padrão não é "uma vez por quadro". Além desses dois erros em seu comentário, suas outras afirmações não contradizem nada do que a resposta diz (embora você pareça pensar que sim).
Mark Amery
7

Se você deseja simplesmente obter a posição atual (por exemplo, quando algum botão é pressionado) em vez de rastreá-la sempre que o usuário rolar a tela, invocar um onScrollouvinte causará problemas de desempenho. Atualmente, a maneira com melhor desempenho de simplesmente obter a posição de rolagem atual é usando o pacote react-native-invoke . Existe um exemplo para isso exatamente, mas o pacote faz várias outras coisas.

Leia sobre isso aqui. https://medium.com/@talkol/invoke-any-native-api-directly-from-pure-javascript-in-react-native-1fb6afcdf57d#.68ls1sopd

Teodors
fonte
2
Você sabe se existe uma maneira mais limpa (um pouco menos hacky) de solicitar o deslocamento ou ainda é a melhor maneira de fazer isso? (Eu me pergunto por que esta resposta não foi votada, pois é a única que parece fazer sentido)
cglacet
1
O pacote não está mais lá, ele se mudou para algum lugar? Github está exibindo 404.
Noitidart
6

Para obter x / y depois que a rolagem terminou como as perguntas originais solicitavam, a maneira mais fácil é provavelmente 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
fonte
-1; onMomentumScrollEndsó é acionado se a visualização de rolagem tiver algum impulso quando o usuário a soltar. Se eles se soltarem enquanto a visualização de rolagem não estiver se movendo, você nunca obterá nenhum dos eventos de momentum.
Mark Amery
1
Testei onMomentumScrollEnd e era impossível para mim não acioná-lo, tentei rolar de várias maneiras, tentei liberar o toque quando o rolo estava na posição final e também foi acionado. Portanto, esta resposta está correta até onde pude testá-la.
fermmm
6

As respostas acima informam como obter a posição usando diferentes APIs onScroll, onMomentumScrollEndetc; Se você quiser saber o índice da página, pode calculá-lo usando o valor de deslocamento.

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

insira a descrição da imagem aqui

No iOS, a relação entre ScrollView e a região visível é a seguinte: A imagem vem de https://www.objc.io/issues/3-views/scroll-view/

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

RY Zheng
fonte
Se você deseja obter o "índice" do item atual, esta é a resposta correta. Você pega event.nativeEvent.contentOffset.x / deviceWidth. Obrigado pela ajuda!
Sara Inés Calderón
1

Foi assim que acabou obtendo a posição de rolagem atual ao vivo da visualização. Tudo o que estou fazendo é usar o ouvinte de evento on scroll e o scrollEventThrottle. Em seguida, estou passando-o como um evento para lidar com a função de rolagem que fiz e atualizando meus adereços.

 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
fonte
é bom atualizar o estado em onScroll considerando o desempenho?
Hrishi
1

o uso de onScroll entra em um loop infinito. onMomentumScrollEnd ou onScrollEndDrag pode ser usado no lugar

kmehmet
fonte
0

Quanto à página, estou trabalhando em um componente de ordem superior que usa basicamente os métodos acima para fazer exatamente isso. Na verdade, leva um pouco de tempo quando você chega às sutilezas, como layout inicial e alterações de conteúdo. Não alegarei ter feito isso 'corretamente', mas de certa forma consideraria a resposta correta usar um componente que faça isso de forma cuidadosa e consistente.

Veja: react-native-paged-scroll-view . Adoraria receber feedback, mesmo que eu tenha feito tudo errado!


fonte
1
isso é incrível. Obrigado por fazer isso. Eu achei isso muito útil imediatamente.
Marty Cortez de
Tão feliz! Agradeço muito o feedback!
1
Um pedido de desculpas -1, já que o projeto admite abertamente que não é mantido e que o componente tem "alguns bugs feios" .
Mark Amery
Obrigado pelo feedback @MarkAmery. :) Encontrar tempo para o código aberto não é fácil.
-3

Acredito contentOffsetque dará a você um objeto contendo o deslocamento de rolagem superior esquerdo:

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

Colin Ramsay
fonte
4
Não é um setter, não um getter?
Aaron Wang
1
-1 isso é um absurdo; a documentação que você vinculou é para uma propriedade JSX, não uma propriedade JavaScript, e só pode ser usada para definir um deslocamento, não para obter um.
Mark Amery