Czy można przewijać ScrollView do dołu?

90

W przypadku aplikacji podobnej do czatu chcę, aby ScrollViewkomponent był przewijany do dołu, ponieważ najnowsze wiadomości pojawiają się pod starszymi. Czy możemy dostosować pozycję przewijania ScrollView?

Stirman
źródło
3
Nie wiem nic o natywnym reagowaniu, ale pamiętaj, że czasami użytkownicy chcą przewinąć w górę, aby wyświetlić historię czatów. Upewnij się, że przewijasz czat tylko wtedy, gdy czat został już przewinięty do samego końca po nadejściu nowej wiadomości.
David
To jest dobry pomysł. Śledzimy ten problem tutaj: github.com/facebook/react-native/issues/107
Eric Vicenti
Właśnie odpowiedziałem na podobne pytanie, sprawdź to tutaj - stackoverflow.com/a/32652967/1541508
Kohver
Czy to odpowiada na twoje pytanie? Jak przewinąć do dołu React Native ListView
TripleM

Odpowiedzi:

166

W przypadku React Native 0.41 i nowszych możesz to zrobić za pomocą wbudowanej scrollToEndmetody:

<ScrollView
    ref={ref => {this.scrollView = ref}}
    onContentSizeChange={() => this.scrollView.scrollToEnd({animated: true})}>
</ScrollView>
Aniruddha Shevle
źródło
3
Z responsywności 0.55.4 i nowszych musiałem użyć rootinaczej, scrollToEnd jest niezdefiniowana. tj.this.scrollView.root.scrollToEnd
Simon
4
Obecnie użycie react native 0.58.6 i użycie w rootrzeczywistości generuje niezdefiniowany błąd.
Jose Bernhardt
1
Czy tak powinno być: ref={ref => {this.scrollView = ref}}ponieważ nie chcesz zwracać zadania
Hampycalc
26

Użyj onContentSizeChange, aby śledzić dolne Y widoku przewijania (wysokość)

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

var _scrollToBottomY

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

następnie użyj nazwy referencyjnej widoku przewijania, np

this.refs.scrollView.scrollTo(_scrollToBottomY);
drikoda
źródło
2
Zwróć uwagę, że powoduje to przewijanie widoku całkowicie w dół, więc widok jest zasadniczo niewidoczny (chyba że coś do niego dodano po pomiarze). Ma to sens, ponieważ kod przewija się do wysokości przewijanego obszaru, a nie do wysokości dzieci przepełniających przewijalny obszar (prawdopodobnie NIE to, czego chciał OP). Zamiast tego zobacz stackoverflow.com/a/39563193/1526986 .
xixixao
21

Oto najprostsze rozwiązanie, jakie mogłem wymyślić:

 <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
źródło
1
Kiedy używam tej lub innych podobnych odpowiedzi, powoduje to, że pozycje na liście znikają (wygląda na to, że przewija się za daleko, ale nie jestem pewien). Drobne przewijanie sprawia, że ​​wszystko pojawia się ponownie i wygląda na to, że wraca do widoku (stąd teoria przewijania zbyt daleko). Jakiś oczywisty pomysł, dlaczego tak może być?
tscizzle
16

Dla każdego, kto pisze komponent funkcyjny, który może używać hooka,

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
źródło
1
proste i eleganckie. doskonale robi to, co chciałem! Dziękuję Ci!
Qamar Stationwala
6

Na wypadek, gdyby ktoś w 2020 roku lub później zastanawiał się, jak to osiągnąć za pomocą funkcjonalnych komponentów. Oto jak zrobiłem:

ref={ref => scrollView = ref }
onContentSizeChange={() => scrollView.scrollToEnd({ animated: true })}>
Marlon Englemam
źródło
Uwaga: możesz wspomnieć, że używasz useref. większość ppl nie wie, jak używać ref z hookami.
ʎzɐɹƆ
tak. Chodzi o to, że zadziałało nawet bez użycia hooka useRef. Ale właściwym sposobem jest użycie go! Masz rację
Marlon Englemam
5

wypróbuj to rozwiązanie

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
źródło
dlaczego ref=? wydaje się być reliktem
twórców
4

Możesz odwrócić widok przewijania / listy za pomocą react-native-invertible-scroll-viewmodułu , a następnie po prostu zadzwoń, ref.scrollTo(0)aby przewinąć do dołu.

Zainstaluj moduł:

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

Następnie zaimportuj komponent:

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

Następnie użyj następującego JSX zamiast a ScrollView:

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

To działa, ponieważ react-native-invertible-scroll-viewodwraca zawartość całego widoku. Zwróć uwagę, że elementy podrzędne widoku będą również renderowane w odwrotnej kolejności do normalnego widoku - pierwsze dziecko pojawi się na dole .

irfaan
źródło
3

Właściwie odpowiedź @Aniruddha nie działa dla mnie, ponieważ używam, "react-native": "0.59.8"a this.scrollView.root.scrollToEndtakże jest niezdefiniowana

ale wymyśliłem to i to działa this.scrollView.scrollResponderScrollToEnd({animated: true});

Jeśli używasz, 0.59.8to zadziała LUB jeśli używasz późniejszej wersji i to działa dla Ciebie. dodaj wersje w tej odpowiedzi, aby inni nie mieli problemów.

Pełny kod tutaj

<ScrollView
    ref={ref => this.scrollView = ref}
    onContentSizeChange={(contentWidth, contentHeight)=>{        
        this.scrollView.scrollResponderScrollToEnd({animated: true});
    }}>
</ScrollView>
RajnishCoder
źródło
1

Ta metoda jest trochę hakerska, ale nie mogłem znaleźć lepszego sposobu

  1. Najpierw dodaj ref do swojego ScrollView.
  2. Po drugie dodaj fikcyjny widok przed zamknięciem tagu ScrollView
  3. Uzyskaj pozycję y tego fałszywego widoku
  4. Ilekroć chcesz przewinąć do dołu, przewiń do pozycji fałszywego widoku

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
źródło
To nie jest dokładnie to, o co proszono; przewija się do dołu po dotknięciu, zamiast utrzymywać widok przewijania przewijany do dołu po dodaniu zawartości. W każdym razie scrollToBottomsprawia , że takie podejście jest przestarzałe w nowszych wersjach React Native.
Mark Amery,
1

To odpowiada na Twoje pytanie: https://stackoverflow.com/a/39563100/1917250

Musisz stale przewijać do dołu strony, obliczając wysokość zawartości minus wysokość jej scrollView.

Paul Rada
źródło
To jest prawidłowa odpowiedź na pytanie. Możesz być sprytny, kiedy wykonać przewijanie, odpowiedź w linku pokazuje, jak uzyskać potrzebne liczby. W szczególności, jeśli dodajesz komponenty do listy, wysokość zawartości nie będzie uwzględniać ich po wywołaniu komponentu componentDidUpdate, więc chcesz pamiętać, że zamiast tego chcesz przewijać i przewijać onContentSizeChange.
xixixao
1

Jeśli używasz TypeScript, musisz to zrobić

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
źródło
0

Przez chwilę z tym walczyłem i w końcu wymyśliłem coś, co działało naprawdę dobrze i gładko. Jak wielu, .scrollToEndbezskutecznie próbowałem . Rozwiązanie że pracował dla mnie było połączenie onContentSizeChange,onLayout rekwizytów i trochę więcej myślenia wizualnie o „co przewijanie do dołu” naprawdę znaczy. Ostatnia część wydaje mi się teraz trywialna, ale zajęło mi całe wieki, zanim to zrozumiałem.

Biorąc pod uwagę, że ScrollViewma stałą wysokość, gdy wysokość zawartości przekracza wysokość kontenera, obliczyłem przesunięcie, odejmując prevHeight(stałą wysokość ScrollView) dla currHeight(wysokość zawartości). Jeśli możesz sobie to wyobrazić wizualnie, przewijanie do tej różnicy w osi Y przesuwa wysokość zawartości nad kontenerem o to przesunięcie. Zwiększyłem swoje przesunięcie i ustawiłem obecną wysokość zawartości na poprzednią wysokość i kontynuowałem obliczanie nowego przesunięcia.

Oto kod. Mam nadzieję, że to komuś pomoże.

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
źródło
0

Myślę, że jest za późno na odpowiedź, ale udało mi się jeden hack! Jeśli używasz FlatList, możesz włączyć invertedwłaściwość, która powraca do ustawienia transform scalena -1(co jest akceptowalne w przypadku innych składników listy, takich jak ScrollView...). Kiedy renderujesz swoje FlatListdane, zawsze będzie to na dole!

Abdumutal Abdusamatov
źródło
-1

Spróbuj ustawić właściwość contentOffset widoku przewijania na bieżącą wysokość treści.

Aby osiągnąć to, czego potrzebujesz, możesz to zrobić w ramach wydarzenia onScroll. Może to jednak niekorzystnie wpłynąć na wrażenia użytkownika, więc bardziej przyjazne dla użytkownika może być robienie tego tylko po dołączeniu nowej wiadomości.

Jacek
źródło
-1; nie. Podobnie jak w przypadku wielu odpowiedzi tutaj, powoduje to przewijanie w ScrollViewdół do poniżej całej zawartości, a nie tylko przewijanie w dół.
Mark Amery,
-3

Trochę późno ... ale spójrz na to pytanie. Przewiń do góry ScrollView

ScrollView ma metodę „scrollTo”.

George B.
źródło
1
Tak, istnieje metoda scrollTo w ScrollView, ale pytanie dotyczy konkretnie przewijania do dołu, co nie jest proste.
Southerneer