Подтвердить что ты не робот

Как прокрутить до нижней части React Native ListView

Я пишу приложение с помощью React Native. Он имеет ListView, чтобы отобразить список элементов.

Я добавляю новые элементы внизу, когда есть новые. Я хотел бы прокрутить вниз в ListView, чтобы автоматически показывать новые элементы. Как я могу это сделать?

4b9b3361

Ответ 1

Начиная с React Native 0.41, ListView и ScrollView имеют scrollToEnd(). ListView документирован по адресу https://facebook.github.io/react-native/docs/listview.html#scrolltoend.

Вы должны будете использовать ref, чтобы сохранить ссылку на ваш ListView, когда вы сделать его:

<ListView
  dataSource={yourDataSource}
  renderRow={yourRenderingCallback}
  ref={listView => { this.listView = listView; }}
</ListView>

Затем вы можете просто вызвать this.listView.scrollToEnd() чтобы прокрутить до конца списка. Если вы хотите делать это каждый раз, когда изменяется содержимое ListView (например, при добавлении контента), то делайте это из обратного вызова ListView onContentSizeChange prop, как в ScrollView.

Ответ 2

Я решаю это для ScrollView. Вот простой пример:

class MessageList extends Component {
    componentDidUpdate() {
        let innerScrollView = this._scrollView.refs.InnerScrollView;
        let scrollView = this._scrollView.refs.ScrollView;

        requestAnimationFrame(() => {
            innerScrollView.measure((innerScrollViewX, innerScrollViewY, innerScrollViewWidth, innerScrollViewHeight) => {
                scrollView.measure((scrollViewX, scrollViewY, scrollViewWidth, scrollViewHeight) => {
                    var scrollTo = innerScrollViewHeight - scrollViewHeight + innerScrollViewY;

                    if (innerScrollViewHeight < scrollViewHeight) {
                        return;
                    }

                    this._scrollView.scrollTo(scrollTo);
                });
            });
        });
    }

    render() {
        return (
            <ScrollView ref={component => this._scrollView = component}>
                {this.props.messages.map((message, i) => {
                    return <Text key={i}>{message}</Text>;
                })}
            </ScrollView>
        );
    }
}

Спасибо за @ccheever

Ответ 3

Theres довольно простое решение этой проблемы. Вы можете обернуть свой ListView внутри компонента Scrollview. Это обеспечит все необходимые методы для определения нижней позиции вашего списка.

Сначала оберните ваш listView

<ScrollView> 
  <MyListViewElement />
</ScrollView>

Затем используйте метод onLayout, который возвращает высоту компонента (scrollView). и сохранить его в состояние.

// add this method to the scrollView component
onLayout={ (e) => {

  // get the component measurements from the callbacks event
  const height = e.nativeEvent.layout.height

  // save the height of the scrollView component to the state
  this.setState({scrollViewHeight: height })
}}

Затем используйте метод onContentSizeChange, который возвращает высоту внутренних компонентов (listView). и сохранить его в состояние. Это будет происходить каждый раз, когда вы добавляете, удаляете элемент из списка или изменяете высоту. Практически каждый раз, когда кто-то добавляет новое сообщение в ваш список.

onContentSizeChange={ (contentWidth, contentHeight) => {

  // save the height of the content to the state when theres a change to the list
  // this will trigger both on layout and any general change in height
  this.setState({listHeight: contentHeight })

}}

Для прокрутки вам понадобится метод scrollTo, найденный внутри ScrollView. Вы можете получить к нему доступ, сохранив его в ссылке в таком состоянии.

<ScrollView
  ref={ (component) => this._scrollView = component } 
  …
  >
</ScrollView>

Теперь у вас есть все, что вам нужно рассчитать и запустить прокрутку внизу списка. Вы можете сделать это в любом месте вашего компонента, я добавлю его в componentDidUpdate() чтобы при каждом отображении компонента он прокручивался вниз.

  componentDidUpdate(){
    // calculate the bottom
    const bottomOfList =  this.state.listHeight - this.state.scrollViewHeight

    // tell the scrollView component to scroll to it
    this.scrollView.scrollTo({ y: bottomOfList })   
 }

И это все. Вот так должен выглядеть ваш ScrollView в конце

<ScrollView
  ref={ (component) => this._scrollView = component }

  onContentSizeChange={ (contentWidth, contentHeight) => {
    this.setState({listHeight: contentHeight })
  }}    

  onLayout={ (e) => {
    const height = e.nativeEvent.layout.heigh
    this.setState({scrollViewHeight: height })
  }}
  > 
  <MyListViewElement />
</ScrollView>

ПРОСТОЙ ПУТЬ Я использовал ListView и обнаружил, что сделать это проще, используя scrollView, для простоты, я рекомендую его. Вот прямая копия моего модуля сообщений для функции прокрутки до дна. Надеюсь, поможет.

class Messages extends Component {
  constructor(props){
    super(props)
    this.state = {
      listHeight: 0,
      scrollViewHeight: 0
    }
  }
  componentDidUpdate(){
    this.scrollToBottom()
  }
  scrollToBottom(){
    const bottomOfList =  this.state.listHeight - this.state.scrollViewHeight
    console.log('scrollToBottom');
    this.scrollView.scrollTo({ y: bottomOfList })
  }
  renderRow(message, index){
    return (
        <Message
          key={message.id}
          {...message}
        />
    );
  }
  render(){
    return(
      <ScrollView
        keyboardDismissMode="on-drag"
        onContentSizeChange={ (contentWidth, contentHeight) => {
          this.setState({listHeight: contentHeight })
        }}
        onLayout={ (e) => {
          const height = e.nativeEvent.layout.height
          this.setState({scrollViewHeight: height })
        }}
        ref={ (ref) => this.scrollView = ref }>
        {this.props.messages.map(  message =>  this.renderRow(message) )}
      </ScrollView>
    )
  }
}

export default Messages

Ответ 4

У меня была та же проблема, и я придумал это решение:

render()
{
    if("listHeight" in this.state && 
           "footerY" in this.state && 
               this.state.footerY > this.state.listHeight)
    {
        var scrollDistance = this.state.listHeight - this.state.footerY;
        this.refs.list.getScrollResponder().scrollTo(-scrollDistance);
    }

    return (
            <ListView ref="list"

                onLayout={(event) => {

                    var layout = event.nativeEvent.layout;

                    this.setState({
                        listHeight : layout.height
                    });

                }}
                renderFooter={() => {

                    return <View onLayout={(event)=>{

                        var layout = event.nativeEvent.layout;

                        this.setState({
                            footerY : layout.y
                        });

                    }}></View>
                }}
            />
    )
}

В принципе, я делаю пустой нижний колонтитул, чтобы установить смещение Y нижней части списка. Из этого я могу получить смещение прокрутки вниз, основываясь на высоте контейнера списка.

ПРИМЕЧАНИЕ.. Последнее условие if проверяет, переполняет ли длина содержимого высоту списка и только прокручивает, если это произойдет. Нужно ли вам это или нет, зависит от вашего дизайна!

Надеемся, что это решение поможет другим в том же положении.

FWIW Мне не понравился плагин InvertibleScrollView, обсуждаемый в другом ответе, потому что он выполняет масштабное преобразование во всем списке и каждом элементе списка. который звучит дорого!

Ответ 5

Пожалуйста, проверьте это: https://github.com/650Industries/react-native-invertible-scroll-view

Этот компонент инвертирует исходный вид прокрутки. Поэтому, когда появляются новые предметы, он автоматически прокручивается до нижней части.

Однако обратите внимание на две вещи.

  • Ваш "массив данных" также должен быть возвращен. То есть, это должно быть

    [new_item, old_item]
    

    и любой новый элемент должен быть вставлен на передний край.

  • Хотя они используют ListView в своем примере README, все еще есть некоторые недостатки при использовании этого плагина с ListView. Вместо этого я предлагаю вам просто использовать ScrollView, который работает очень хорошо.

Пример перевернутого вида прокрутки:

var MessageList = React.createClass({
  propTypes: {
    messages: React.PropTypes.array,
  },

  renderRow(message) {
    return <Text>{message.sender.username} : {message.content}</Text>;
  },

  render() {
    return (
      <InvertibleScrollView
        onScroll={(e) => console.log(e.nativeEvent.contentOffset.y)}
        scrollEventThrottle={200}
        renderScrollView={
          (props) => <InvertibleScrollView {...props} inverted />
        }>
        {_.map(this.props.messages, this.renderRow)}
      </InvertibleScrollView>
    );
  }
});

Ответ 6

Я боюсь, что это не ультра чистый способ сделать это. Поэтому мы будем делать это вручную, не волнуйтесь, его легко и не беспорядочно.

Шаг 1. Объявите эти переменные в своем состоянии.

constructor(props) {
  super(props);
  this.state={
    lastRowY:0,
  }
}

Шаг 2. Создайте функцию scrollToBottom следующим образом:

scrollToBottom(){
  if(!!this.state.lastRowY){
    let scrollResponder = this.refs.commentList.getScrollResponder();
    scrollResponder.scrollResponderScrollTo({x: 0, y: this.state.lastRowY, animated: true});
  }
}

Шаг 3: добавьте следующие свойства в ListView:

  • A ref, чтобы иметь к нему доступ (в этом примере commentList)

     ref="commentList"
    
  • Следующая функция onLayout внутри renderRow в элементе строки:

     onLayout={(event) => {
            var {y} = event.nativeEvent.layout;
            this.setState({
                lastRowY : y
            });
    

Ваш ListView должен выглядеть примерно так:

 <ListView
     ref="commentList"
     style={styles.commentsContainerList}
     dataSource={this.state.commentDataSource}
     renderRow={()=>(
         <View 
             onLayout={(event)=>{
                 let {y} = event.nativeEvent.layout;
                 this.setState({
                     lastRowY : y
                 });
             }}
         />
         </View>
     )}
 />

Шаг 4. Затем в любом месте вашего кода просто вызовите this.scrollToBottom();.

Enjoy..

Ответ 7

Для тех, кто заинтересован в его реализации с помощью ListView

// initialize necessary variables
componentWillMount() {
  // initialize variables for ListView bottom focus
  this._content_height = 0;
  this._view_height = 0;
}
render() {
  return (
    <ListView
      ...other props
      onLayout = {ev => this._scrollViewHeight = ev.nativeEvent.layout.height}
      onContentSizeChange = {(width, height)=>{
        this._scrollContentHeight = height;
        this._focusOnBottom();
      }}
      renderScrollComponent = {(props) =>
        <ScrollView
          ref = {component => this._scroll_view = component}
          onLayout = {props.onLayout}
          onContentSizeChange = {props.onContentSizeChange} /> } />
  );
}
_focusOnLastMessage() {
  const bottom_offset = this._content_height - this._view_height;
  if (bottom_offset > 0 &&)
    this._scroll_view.scrollTo({x:0, y:scrollBottomOffsetY, false});
}

Вы можете использовать функцию _focusOnLastMessage везде, где хотите, например, я использую ее всякий раз, когда изменяется размер содержимого. Я проверил коды с помощью [email protected]

Ответ 8

Таким образом, чтобы сделать автоматическую прокрутку до конца ListView, вы должны добавить prop 'onContentSizeChange' в ListView и взять соответствующий контент из его аргумента, например так:

<ListView
              ref='listView'
              onContentSizeChange={(contentWidth, contentHeight) => {
                this.scrollTo(contentHeight);
               }}
              ...
            />

Так что для моего случая я должен визуализировать мой список по вертикали, поэтому я использовал contentHeight, в случае горизонтального перечисления вы просто должны использовать contentWeight.

Здесь функция scrollTo должна быть:

scrollTo = (y) => {
if (y<deviceHeight-120) {
  this.refs.listView.scrollTo({ y: 0, animated: true })
}else{
  let bottomSpacing = 180;
  if (this.state.messages.length > 0) {
    bottomSpacing = 120;
  }

  this.refs.listView.scrollTo({ y: y - deviceHeight + bottomSpacing, animated: true })
}

}

Это. Я надеюсь, что это объяснение поможет кому-то сэкономить время.

Ответ 9

расширенный от @jonasb попробуйте этот код ниже, используйте событие onContentSizeChange для выполнения метода scrollToEnd

<ListView
  ref={ ( ref ) => this.scrollView = ref }
  dataSource={yourDataSource}
  renderRow={yourRenderingCallback}
  onContentSizeChange={ () => {        
     this.scrollView.scrollToEnd( { animated: true })
  }} 
</ListView>

Ответ 10

Я объединил самые простые решения, которые помогли мне. При этом ScrollView прокручивается вниз не только при изменении содержимого, но и при изменении высоты. Только проверено с IOS.

<ScrollView
  ref="scrollView"
  onContentSizeChange={(width, height) =>
    this.refs.scrollView.scrollTo({ y: height })
  }
  onLayout={e => {
    const height = e.nativeEvent.layout.height;
    this.refs.scrollView.scrollTo({ y: height });
    }
  }
>
  ......
</ScrollView>

"реакция-натив": "0.59.10",

Ответ 11

Вот что я использую с React Native 0.14. Эта оболочка ListView прокручивается вниз, когда:

  • Изменение высоты содержимого
  • Изменение высоты контейнера
  • Клавиатура становится видимой или невидимой.

Он отражает стандартное поведение большинства чат-приложений.

Эта реализация зависит от деталей реализации RN0.14 ListView и, следовательно, может потребоваться корректировка для совместимости с будущими версиями React Native



    var React = require('react-native');
    var RCTDeviceEventEmitter = require('RCTDeviceEventEmitter');
    var RCTUIManager = require('NativeModules').UIManager;

    var {
      ListView,
      } = React;

    export default class AutoScrollListView extends React.Component {
      componentWillMount() {
        this._subscribableSubscriptions = [];
      }

      componentDidMount() {
        this._addListenerOn(RCTDeviceEventEmitter, 'keyboardWillShow', this._onKeyboardWillShow);
        this._addListenerOn(RCTDeviceEventEmitter, 'keyboardWillHide', this._onKeyboardWillHide);

        var originalSetScrollContentLength = this.refs.listView._setScrollContentLength;
        var originalSetScrollVisibleLength = this.refs.listView._setScrollVisibleLength;

        this.refs.listView._setScrollContentLength = (left, top, width, height) => {
          originalSetScrollContentLength(left, top, width, height);
          this._scrollToBottomIfContentHasChanged();
        };

        this.refs.listView._setScrollVisibleLength = (left, top, width, height) => {
          originalSetScrollVisibleLength(left, top, width, height);
          this._scrollToBottomIfContentHasChanged();
        };
      }

      componentWillUnmount() {
        this._subscribableSubscriptions.forEach(
          (subscription) => subscription.remove()
        );
        this._subscribableSubscriptions = null;
      }

      render() {
        return 
      }

      _addListenerOn = (eventEmitter, eventType, listener, context) => {
        this._subscribableSubscriptions.push(
          eventEmitter.addListener(eventType, listener, context)
        );
      }

      _onKeyboardWillShow = (e) => {
        var animationDuration = e.duration;
        setTimeout(this._forceRecalculationOfLayout, animationDuration);
      }

      _onKeyboardWillHide = (e) => {
        var animationDuration = e.duration;
        setTimeout(this._forceRecalculationOfLayout, animationDuration);
      }

      _forceRecalculationOfLayout = () => {
        requestAnimationFrame(() => {
          var scrollComponent = this.refs.listView.getScrollResponder();
          if (!scrollComponent || !scrollComponent.getInnerViewNode) {
            return;
          }
          RCTUIManager.measureLayout(
            scrollComponent.getInnerViewNode(),
            React.findNodeHandle(scrollComponent),
            () => {}, //Swallow error
            this.refs.listView._setScrollContentLength
          );
          RCTUIManager.measureLayoutRelativeToParent(
            React.findNodeHandle(scrollComponent),
            () => {}, //Swallow error
            this.refs.listView._setScrollVisibleLength
          );
        });
      }

      _scrollToBottomIfContentHasChanged = () => {
        var scrollProperties = this.refs.listView.scrollProperties;
        var hasContentLengthChanged = scrollProperties.contentLength !== this.previousContentLength;
        var hasVisibleLengthChanged = scrollProperties.visibleLength !== this.previousVisibleLength;

        this.previousContentLength = scrollProperties.contentLength;
        this.previousVisibleLength = scrollProperties.visibleLength;

        if(!hasContentLengthChanged && !hasVisibleLengthChanged) {
          return;
        }

        this.scrollToBottom();
      }

      scrollToBottom = () => {
        var scrollProperties = this.refs.listView.scrollProperties;
        var scrollOffset = scrollProperties.contentLength - scrollProperties.visibleLength;
        requestAnimationFrame(() => {
          this.refs.listView.getScrollResponder().scrollTo(scrollOffset);
        });
      }
    }


Ответ 12

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

listViewHeight может быть определен различными способами, но я лично получаю его из анимации, которая использовалась для анимации высоты списка, чтобы убрать клавиатуру.

render() {
  let initialListSize = 5

  if (this.state.shouldScrollToBottom) {
    initialListSize = 100000 // A random number that going to be more rows than we're ever going to see in the list.
  }

  return (<ListView
            ref="listView"
            initialListSize={ initialListSize }
            otherProps...
            renderFooter={() => {
              return <View onLayout={(event)=>{

                if (this.state.shouldScrollToBottom) {
                  const listViewHeight = this.state.height._value
                  this.refs.listView.scrollTo({y: event.nativeEvent.layout.y - listViewHeight + 64}) // 64 for iOS to offset the nav bar height

                  this.setState({shouldScrollToBottom: false})
                }
              }}></View>
            }}
          />)
}

scrollToBottom() {
  self.setState({shouldScrollToBottom: true})
}

Ответ 13

Лучшее решение для Liked @ComethTheNerd, и здесь более регулярное (прошло все правила ESLinting airbnb):

  state={
    listHeight: 0,
    footerY: 0,
  }

  // dummy footer to ascertain the Y offset of list bottom
  renderFooter = () => (
    <View
      onLayout={(event) => {
        this.setState({ footerY: event.nativeEvent.layout.y });
      }}
    />
  );

...

  <ListView
    ref={(listView) => { this.msgListView = listView; }}
    renderFooter={this.renderFooter}
    onLayout={(event) => {
      this.setState({ listHeight: event.nativeEvent.layout.height });
    }}
  />

И вызовите метод scrollToBottom следующим образом:

  componentDidUpdate(prevProps, prevState) {
    if (this.state.listHeight && this.state.footerY &&
        this.state.footerY > this.state.listHeight) {
      // if content is longer than list, scroll to bottom
      this.scrollToBottom();
    }
  }

  scrollToBottom = () => {
    const scrollDistance = this.state.footerY - this.state.listHeight;
    this.msgListView.scrollTo({ y: scrollDistance, animated: true });
  }

Ответ 14

Это все, что вам нужно

    componentDidUpdate: function() {

        this._scrollToBottom();

    },
    _scrollToBottom: function() {

        var ul = this.refs.messageList;
        ul.scrollTop = ul.scrollHeight;

    },