React Native - 无限滚动ListView

时间:2015-10-27 04:34:02

标签: android ios listview react-native

我正在尝试在React Native中实现无限滚动ListView。这通过检测列表视图何时到达其内容长度的底部并触发另一个API来工作。收到响应后,新数据将附加到原始数据(这在我的Flux存储中完成)。但是在我的组件中,我通过以下方式更新了dataSource:

setState({
    dataSource: this.state.dataSource.cloneWithRows(Store.getLatestData())
})

我似乎遇到的问题是每次发生这种情况时,ListView都会刷新并丢失其滚动位置(它返回到顶部)。如何在不丢失当前位置的情况下添加新数据?

谢谢!

更新

我想在添加新数据之前保存滚动偏移,然后在添加数据后滚动到原点偏移。 我试图这样做:

this._listView.getScrollResponder().scrollTo(offset, 0);

其中this._listView是ListView组件的引用。但是,每当我访问它时,this._listView似乎总是为null。我的猜测是,在重新渲染过程中,对listview的引用在一瞬间是空的吗?

更新2

了解更多信息:

我的getInitialState是:

getInitialState: function() {
    var ds = new ListView.DataSource({
        rowHasChanged: (r1, r2) => r1 !== r2,
        sectionHeaderHasChanged: (s1, s2) => true
    });
    return {
        dataSource: ds.cloneWithRows(LeaderboardsStore.getLeaderboard()),
        //some other state too
    }

一旦我检测到用户已滚动到底部,我就通过fetch API启动API调用,该API返回新数据并通过concat附加到原始数组。保存数组的存储(磁通存储)会发出更改事件。

在我的onStoreChange回调中,我按如下方式重新分配dataSource:

setState({
    dataSource: this.state.dataSource.cloneWithRows(Store.getLatestData())
})

按预期更新我的ListView(滚动位置有不必要的更改)。

我的渲染方法看起来像(为简单起见):

render:function() {
    return (
        <ListView 
            dataSource={this.state.dataSource}
            renderRow={this._renderRow}
            ref={(component) => {this._listView = component;}}
         />
    );
}

我尝试将onStoreChange回调更改为:

onStoreChangeCallback: function() {
    this._scrollOffset = this._listView.scrollProperties.offset;
    this.setState({
        dataSource: this.state.dataSource.cloneWithRows(Store.getLatestData())
    }, function() {
        if (this._scrollOffset !== undefined) {
            this._listView.getScrollResponder().scroll(offset, 0);
        }
    });
}

但是,this._listView在getScrollResponder行似乎为null:(

2 个答案:

答案 0 :(得分:0)

在没有看到更多代码的情况下知道这个问题有点难,但是,我假设React Native并不了解您已将项目合并到原始列表中。请务必使用concat加入项目:

incidents = _.uniq(incidents.concat(payload.action.response), "id") IncidentsStore.emitChange(RECEIVED_EVENT)

在这个例子中,我使用concat然后使用lodash以确保我不会偶然发生双打。

此外,您的getLatestData应该返回原始项目的副本(slice(0)是一种简单的方法),如下所示:

getIncidents: (state) ->
  dataSource: state.dataSource.cloneWithRows(incidents.slice(0))
  loaded: true

希望有所帮助,如果您有疑问,请告诉我。

<强>更新

您不需要保存位置。您的列表视图应如下所示:

<ListView
  dataSource={this.state.dataSource}
  renderRow={this.renderRow}
  style={styles.listView}
  onEndReachedThreshold=1200
  onEndReached={this.fetchMoreLeaders}
/>

在我的组件中,我添加了以下监听器:

componentDidMount: function() {
  this.pageNumber = 0;
  IncidentsStore.addLeadersReceivedListener(this.updateList);
  return this.fetchMoreLeaders();
},

fetchMoreLeaders: function() {
  return LeadersActions.fetchLeaders(this.pageNumber += 1);
},

updateList: function() {
  return this.setState(LeadersStore.getLeaders(this.state));
},

我的商店将返回以下内容:

getLeaders: function(state) {
  return {
    dataSource: state.dataSource.cloneWithRows(leaders.slice(0)),
    loaded: true
  };
},

当新数据出现时,我是我之前用_.uniq

写的concat

答案 1 :(得分:0)

我不确定你是否能够解决这个问题,但我遇到了同样的问题而且我正在添加适合我的方法。

首先将数据设置为状态,或者您可以使用getInitialState

state = {
    loading: true,
    currentPage: 1,
    data: {
      collection: []
    }
  }

然后在componentDidMount中获取所需数据,此处user是我的服务,它只是进行api调用的包装器。

componentDidMount() {
    this.setState({loading: true}, ()=>{
        user.artists().then((data)=>{
          const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
          const dataSource = ds.cloneWithRows(data.collection);
          this.setState({
            data,
            dataSource,
            loading: false
          });
          console.log(data.last_page);
        }).catch((err)=>{
          console.log(err);
        }).done();
      });
  }

这是我呈现ListView

的方式
renderPartial(datas) {
    if (datas.length === 0) {
      return <View/>;
    }
    const ds = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    const dataSource = ds.cloneWithRows(datas);
    return (
      <ListView
        enableEmptySections={true}
        dataSource={dataSource}
        onEndReached={()=>this.loadOtherPage()}
        onEndReachedThreshold={400}
        renderRow={(rowData) => this.renderContent(rowData)}
      />
    );
  }

onEndReached会触发this.loadOtherPage函数,就像这样,在这里您可以看到我只是将新结果推送到数据中 data.collection.push(item); 这样,新数据与现有数据连接,视口保持在当前位置而不是返回顶部。

loadOtherPage() {
    if (this.state.currentPage !== this.state.data.last_page && !this.state.loading) {
      this.setState({currentPage: this.state.currentPage + 1, loading: true}, ()=>{
        user.artists({page: this.state.currentPage})
          .then((response)=>{
            const data = this.state.data;
            response.collection.map((item)=>{
              data.collection.push(item);
            });
            this.setState({
              data,
              loading: false});
          }).catch((err)=>{
            console.log(err);
          });
      });
    } else {
      console.log('max page reached or it is still loading');
    }
  }

我希望这有助于某人。