如何滚动到React Native ListView的底部

时间:2015-04-23 16:29:32

标签: react-native

我正在使用React Native编写应用程序。它有一个ListView来显示项目列表。

当有新的项目可用时,我会在底部添加新项目。 我想在ListView中滚动到底部以自动显示新项目。我怎么能这样做?

13 个答案:

答案 0 :(得分:10)

从React Native 0.41开始,ListViewScrollView都有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的{​​{3}}道具回调中执行此操作,就像ScrollView一样。

答案 1 :(得分:8)

我为 ScrollView 解决了这个问题。这是一个简单的例子:

&#xA;&#xA;
  class MessageList extends Component {&#xA; componentDidUpdate(){&#xA; let innerScrollView = this._scrollView.refs.InnerScrollView;&#xA;让scrollView = this._scrollView.refs.ScrollView;&#xA;&#xA; requestAnimationFrame(()=&gt; {&#xA; innerScrollView.measure((innerScrollViewX,innerScrollViewY,innerScrollViewWidth,innerScrollViewHeight)=&gt; {&#xA; scrollView.measure((scrollViewX,scrollViewY,scrollViewWidth,scrollViewHeight)=&gt; { &#xA; var scrollTo = innerScrollViewHeight  -  scrollViewHeight + innerScrollViewY;&#xA;&#xA; if(innerScrollViewHeight&lt; scrollViewHeight){&#xA; return;&#xA;}&#xA;&#xA; this。 _scrollView.scrollTo(scrollTo);&#xA;});&#xA;});&#xA;});&#xA; }&#XA;&#XA; render(){&#xA; return(&#xA;&lt; ScrollView ref = {component =&gt; this._scrollView = component}&gt;&#xA; {this.props.messages.map((message,i)=&gt; {&#xA;返回&lt;文字键= {i}&gt; {message}&lt; / Text&gt ;;&#xA;})}&#xA;&lt; / ScrollView&gt;&#xA;);&#xA; }&#xA;}&#xA;  
&#xA;&#xA;

感谢 @ ccheever

&#XA;

答案 2 :(得分:7)

我遇到了同样的问题,并提出了这个解决方案:

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插件,因为它对整个列表和每个列表项进行了比例转换......听起来很贵!

答案 3 :(得分:7)

这个问题有一个非常简单的解决方案。 您可以将ListView包装在Scrollview组件中。 这将提供确定列表底部位置的所有必要方法。

首先包装listView

<ScrollView> 
  <MyListViewElement />
</ScrollView>

然后使用返回组件(scrollView)高度的onLayout方法。并将其保存到州。

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

然后使用返回内部组件(listView)高度的onContentSizeChange方法。并将其保存到州。每次从列表中添加或删除元素或更改高度时都会发生这种情况。基本上每当有人向您的列表添加新消息时。

onContentSizeChange={ (contentWidth, contentHeight) => {

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

}}

要滚动,您需要在ScrollView中找到scrollTo方法。您可以通过将其保存到状态ref来访问它。

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

现在,您拥有计算所需的一切,并触发滚动到列表底部。您可以选择在组件中的任何位置执行此操作,我会将其添加到componentDidUpdate(),因此无论何时呈现组件,它都会scrollTo在底部。

  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 :(得分:3)

请检查一下: https://github.com/650Industries/react-native-invertible-scroll-view

此组件反转原始滚动视图。因此,当新物品到达时,它会自动滚动到底部。

但请注意两件事

  1. 您的“数据阵列”也需要还原。也就是说,它应该是

    [new_item, old_item]
    

    任何新到货的项目都应插入最前面。

  2. 虽然他们在自述文件示例中使用ListView,但在将此插件与ListView一起使用时仍存在一些缺陷。相反,我建议你只使用ScrollView,它效果很好。

  3. 倒置滚动视图的示例:

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

答案 5 :(得分:1)

我担心这不是一个超级干净的方法。因此,我们将手动完成,不要担心它的简单性和不凌乱。

第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

  • ref可以访问它(在此示例中为commentList

     ref="commentList"
    
  • 您的行元素上的renderRow中的以下onLayout函数:

     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();

享受..

答案 6 :(得分:0)

对于那些只想用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函数,例如,每当内容大小发生变化时我都会使用它。我用react-native@0.32

测试了代码

答案 7 :(得分:0)

因此,为了自动滚动到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 })
}

}

就是这样。我希望我的这个解释可以帮助别人节省时间。

答案 8 :(得分:0)

从@jonasb扩展 在下面尝试以下代码,使用onContentSizeChange事件执行scrollToEnd方法

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

答案 9 :(得分:0)

我结合了对我有用的最简单的解决方案。 这样,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>

“ react-native”:“ 0.59.10”,

答案 10 :(得分:-1)

这是我在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);
        });
      }
    }


答案 11 :(得分:-1)

这是另一种变化。当有人评论时,我个人使用它滚动到列表视图的底部。我更喜欢其他例子,因为它更简洁。

listViewHeight可以通过各种方式确定,但我个人从动画中获取它,用于动画列表视图高度以避开键盘。

DataGridView

答案 12 :(得分:-1)

最喜欢@ComethTheNerd的解决方案,这是一个更常规的(通过所有airbnb的ESlinting规则)版本:

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