É possível manter um ScrollView rolado para a parte inferior?

87

Para um aplicativo semelhante a um bate-papo, quero manter um ScrollViewcomponente rolado para a parte inferior, porque as mensagens mais recentes aparecem abaixo das mais antigas. Podemos ajustar a posição de rolagem de a ScrollView?

Stirman
fonte
3
Não sei nada sobre reagente nativo, mas tenha em mente que às vezes os usuários desejam rolar para cima para ver o histórico de bate-papo. Certifique-se de rolar o bate-papo apenas se o bate-papo já foi rolado até o final quando uma nova mensagem chegar.
David
Essa é uma boa ideia. Estamos rastreando esse problema aqui: github.com/facebook/react-native/issues/107
Eric Vicenti
Acabei de responder a uma pergunta semelhante, verifique aqui - stackoverflow.com/a/32652967/1541508
Kohver
Isso responde sua pergunta? Como rolar para a parte inferior do React Native ListView
TripleM

Respostas:

165

Para React Native 0.41 e posterior , você pode fazer isso com o scrollToEndmétodo integrado :

<ScrollView
    ref={ref => {this.scrollView = ref}}
    onContentSizeChange={() => this.scrollView.scrollToEnd({animated: true})}>
</ScrollView>
Aniruddha Shevle
fonte
3
De react nativo 0.55.4 e superior, tive que usar rootcaso contrário, scrollToEnd é indefinido. iethis.scrollView.root.scrollToEnd
Simon
4
Atualmente, o uso do react nativo 0.58.6 e o ​​uso rootrealmente gera um erro indefinido.
Jose Bernhardt
1
Deveria ser: ref={ref => {this.scrollView = ref}}como você não deseja devolver a tarefa
Hampycalc
25

Use onContentSizeChange para controlar o Y inferior da visualização de rolagem (altura)

https://facebook.github.io/react-native/docs/scrollview.html#oncontentsizechange

var _scrollToBottomY

<ScrollView
    onContentSizeChange={(contentWidth, contentHeight)=>{
    _scrollToBottomY = contentHeight;
    }}>
</ScrollView>

em seguida, use o nome ref da visualização de rolagem, como

this.refs.scrollView.scrollTo(_scrollToBottomY);
drikoda
fonte
2
Observe que isso rola a visualização completamente para o fundo, portanto, a visualização essencialmente não é visível (a menos que você tenha adicionado algo a ela após a medição). Isso faz sentido, pois o código rola até a altura da área rolável, não até a altura dos filhos que ultrapassam a área rolável (provavelmente NÃO o que o OP queria). Em vez disso, consulte stackoverflow.com/a/39563193/1526986 .
xixixao
20

Aqui está a solução mais simples que poderia reunir:

 <ListView
ref={ref => (this.listView = ref)}
onLayout={event => {
    this.listViewHeight = event.nativeEvent.layout.height;
}}
onContentSizeChange={() => {
    this.listView.scrollTo({
        y: this.listView.getMetrics().contentLength - this.listViewHeight
    });
}}
/>;
senornestor
fonte
1
Quando uso esta ou outras respostas semelhantes, os itens da lista desaparecem (parece que está rolando muito , mas não tenho certeza). Rolar um pouquinho faz tudo reaparecer e meio que parece que está deslizando de volta à vista (daí a teoria de rolar muito longe). Alguma ideia óbvia do porquê disso?
tscizzle
11

Para qualquer um que escreva um componente de função que possa usar o gancho,

import React, { useRef } from 'react';
import { ScrollView } from 'react-native';

const ScreenComponent = (props) => {
  const scrollViewRef = useRef();
  return (
    <ScrollView
      ref={scrollViewRef}
      onContentSizeChange={() => scrollViewRef.current.scrollToEnd({ animated: true })}
    />
  );
};
cltsang
fonte
5

tente esta solução

xcode 10.0

RN: 56.0.0

<ScrollView ref="scrollView"
             onContentSizeChange={(width,height) => this.refs.scrollView.scrollTo({y:height})}>  </ScrollView> // OR height -  width
Vishal Vaghasiya
fonte
por que ref=? parece uma relíquia de desenvolvimento da web
YungGun
5

No caso de alguém em 2020 ou mais tarde estar se perguntando como conseguir isso com componentes funcionais. É assim que eu fiz:

ref={ref => scrollView = ref }
onContentSizeChange={() => scrollView.scrollToEnd({ animated: true })}>
Marlon Englemam
fonte
Nota: você pode mencionar que está usando o useref. a maioria das pessoas não sabe como usar ref com ganchos.
ʎzɐɹƆ
sim. A questão é que funcionou mesmo quando não estava usando o gancho useRef. Mas o jeito certo é usar! Você está certo
Marlon Englemam
4

Você pode inverter a visualização de rolagem / lista usando o react-native-invertible-scroll-viewmódulo e , em seguida, basta chamar ref.scrollTo(0)para rolar para baixo.

Instale o módulo:

npm install --save react-native-invertible-scroll-view

Em seguida, importe o componente:

import InvertibleScrollView from 'react-native-invertible-scroll-view';

Em seguida, use o seguinte JSX em vez de ScrollView:

<InvertibleScrollView inverted
    ref={ref => { this.scrollView = ref; }}
    onContentSizeChange={() => {
        this.scrollView.scrollTo({y: 0, animated: true});
    }}
>
    { /* content */ }
</InvertibleScrollView>

Isso funciona porque react-native-invertible-scroll-viewinverte todo o conteúdo da visualização. Observe que os filhos da visualização também serão renderizados na ordem oposta a de uma visualização normal - o primeiro filho aparecerá na parte inferior .

Irfaan
fonte
3

Na verdade, a resposta @Aniruddha não funcionou para mim porque estou usando "react-native": "0.59.8"e this.scrollView.root.scrollToEndtambém é indefinida

mas eu descobri e isso funciona this.scrollView.scrollResponderScrollToEnd({animated: true});

Se você estiver usando, 0.59.8isso funcionará OU se estiver usando uma versão posterior e isso funcionar para você. adicione versões nesta resposta para que outras pessoas não tenham problemas.

Código completo aqui

<ScrollView
    ref={ref => this.scrollView = ref}
    onContentSizeChange={(contentWidth, contentHeight)=>{        
        this.scrollView.scrollResponderScrollToEnd({animated: true});
    }}>
</ScrollView>
RajnishCoder
fonte
1

Este método é um pouco hacky, mas não consegui encontrar uma maneira melhor

  1. Primeiro adicione uma referência ao seu ScrollView.
  2. Em segundo lugar, adicione uma visualização fictícia antes de fechar sua tag ScrollView
  3. Obtenha a posição y dessa vista fictícia
  4. Sempre que você quiser rolar para baixo, vá para a posição da visualização fictícia

var footerY; //Y position of the dummy view
render() {    
    return (
      <View>
          <ScrollView 
          ref='_scrollView'>
            // This is where all the things that you want to display in the scroll view goes
            
            // Below is the dummy View
            <View onLayout={(e)=> {
              footerY = e.nativeEvent.layout.y;
              }}/>
          </ScrollView>
              
          //Below is the button to scroll to the bottom    
          <TouchableHighlight
            onPress={() => { this.refs._scrollView.scrollTo(footerY); }}>
            <Text>Scroll to Bottom</Text>
          </TouchableHighlight>
        
      </View>
    );
  }

Sritam
fonte
Não foi exatamente isso que foi pedido; isso rola para o fundo na torneira, em vez de manter a visualização de rolagem rolada para o fundo à medida que o conteúdo é adicionado. De qualquer forma, scrollToBottomtorna abordagens como essa obsoletas nas versões mais recentes do React Native.
Mark Amery
1

Isso responde à sua pergunta: https://stackoverflow.com/a/39563100/1917250

Você precisa rolar constantemente para a parte inferior da página, calculando a altura do conteúdo menos a altura de seu scrollView.

Paul Rada
fonte
Esta é a resposta correta para a pergunta. Você pode saber quando fazer a rolagem. A resposta no link mostra como obter os números de que precisa. Em particular, se você estiver adicionando componentes à lista, a altura do conteúdo não os incluirá quando o componentDidUpdate for chamado, então você deseja se lembrar que deseja rolar e rolar onContentSizeChange.
xixixao
1

Se você usa TypeScript, você precisa fazer isso

interface Props extends TextInputProps { 
     scrollRef?: any;
}

export class Input extends React.Component<Props, any> {
     scrollRef;
constructor(props) {
    this.scrollRef = React.createRef();
}
<ScrollView
    ref={ref => (this.scrollRef = ref)}
    onContentSizeChange={() => {
    this.scrollRef.scrollToEnd();
    }}
>
</ScrollView>
Forhad
fonte
0

Lutei com isso por um tempo e finalmente descobri algo que funcionou muito bem e suave para mim. Como muitos, tentei .scrollToEndsem sucesso. A solução que funcionou para mim foi uma combinação de onContentSizeChange,onLayout adereços e um pouco mais a pensar visualmente sobre "o que rolar para o fundo" realmente significava. A última parte parece trivial para mim agora, mas levei uma eternidade para descobrir.

Dado que o ScrollViewtem uma altura fixa, quando a altura do conteúdo ultrapassa a altura do contêiner, calculei o deslocamento subtraindo a prevHeight(altura fixa de ScrollView) de currHeight(a altura do conteúdo). Se você puder imaginá-lo visualmente, rolar para essa diferença no eixo y desliza a altura do conteúdo sobre seu contêiner por esse deslocamento. Aumentei meu deslocamento e tornei minha altura de conteúdo atual minha altura anterior e continuei calculando um novo deslocamento.

Aqui está o código. Espero que ajude alguém.

class ChatRoomCorrespondence extends PureComponent {
  currHeight = 0;
  prevHeight = 0;
  scrollHeight = 0;

  scrollToBottom = () => {
    this.refs.scrollView.getScrollResponder().scrollResponderScrollTo({
      x: 0,
      y: this.scrollHeight,
      animated: true
    });
  };

  render() {
    return (
      <ScrollView
        style={Styles.container}
        ref="scrollView"
        onContentSizeChange={(w, h) => {
          this.currHeight = h;

          if (
            this.prevHeight > 0 &&
            this.currHeight - this.scrollHeight > this.prevHeight
          ) {
            this.scrollHeight += this.currHeight - this.prevHeight;
            console.log("--------------------------------------------");
            console.log("Curr: ", this.currHeight);
            console.log("Prev: ", this.prevHeight);
            console.log("Scroll: ", this.scrollHeight);
            this.prevHeight = this.currHeight;
            console.log("PREV: ", this.prevHeight);
            console.log("--------------------------------------------");

            this.scrollToBottom();
          }
        }}
        onLayout={ev => {
          // Fires once
          const fixedContentHeight = ev.nativeEvent.layout.height;
          this.prevHeight = fixedContentHeight;
        }}
      >
        <FlatList
          data={this.props.messages}
          renderItem={({ item }) => (
            <ChatMessage
              text={item.text}
              sender={item.sender}
              time={item.time}
              username={this.props.username}
            />
          )}
          keyExtractor={(item, index) => index.toString()}
        />
      </ScrollView>
    );
  }
}
Nelly Sugu
fonte
0

Acho que é tarde demais para responder, mas consegui um hack! Se você usar FlatListvocê pode ativar inverteda propriedade que cai de volta para definir transform scalea -1(o que é aceitável para outros componentes lista como ScrollView...). Quando você renderiza seus FlatListdados, ele sempre estará no fundo!

Abdumutal Abdusamatov
fonte
-1

Tente definir a propriedade contentOffset da visualização de rolagem para a altura atual do conteúdo.

Para realizar o que você precisa, você pode fazer isso como parte do evento onScroll. No entanto, isso pode causar uma experiência ruim para o usuário, por isso pode ser mais amigável fazer isso apenas quando uma nova mensagem for anexada.

Jack
fonte
-1; Não. Como muitas das respostas aqui, isso tem o efeito de rolar de ScrollViewbaixo para baixo todo o seu conteúdo, em vez de apenas rolar para o final.
Mark Amery
-1

Tive um problema parecido, mas depois de ler esta página (o que me dá dor de cabeça!) Achei esse pacote npm muito legal que inverte o ListView e facilita tudo para um aplicativo de chat !!

Mehdi Sabouri
fonte
-1; isto duplica uma resposta postada anteriormente aqui (e também é um pouco confuso; esta questão é sobre um ScrollView, não `ListView`).
Mark Amery
-3

Um pouco tarde ... mas olhe para esta questão. Role para cima de ScrollView

ScrollView tem um método "scrollTo".

George B
fonte
1
Sim, há um método scrollTo em ScrollView, mas a questão é especificamente sobre rolar para a parte inferior, o que não é simples.
Southerneer