ListView 长列表

适用于显示同类的长列表数据类型,对渲染性能有一定的优化效果。ListView经常和 RefreshControl 搭配使用。

代码演示

自定义容器

Note: you need set height/overflow style.

  1. /* eslint no-dupe-keys: 0, no-mixed-operators: 0 */
  2. import { ListView } from 'antd-mobile';
  3. function MyBody(props) {
  4. return (
  5. <div className="am-list-body my-body">
  6. <span style={{ display: 'none' }}>you can custom body wrap element</span>
  7. {props.children}
  8. </div>
  9. );
  10. }
  11. const data = [
  12. {
  13. img: 'https://zos.alipayobjects.com/rmsportal/dKbkpPXKfvZzWCM.png',
  14. title: 'Meet hotel',
  15. des: '不是所有的兼职汪都需要风吹日晒',
  16. },
  17. {
  18. img: 'https://zos.alipayobjects.com/rmsportal/XmwCzSeJiqpkuMB.png',
  19. title: 'McDonald\'s invites you',
  20. des: '不是所有的兼职汪都需要风吹日晒',
  21. },
  22. {
  23. img: 'https://zos.alipayobjects.com/rmsportal/hfVtzEhPzTUewPm.png',
  24. title: 'Eat the week',
  25. des: '不是所有的兼职汪都需要风吹日晒',
  26. },
  27. ];
  28. let index = data.length - 1;
  29. const NUM_SECTIONS = 5;
  30. const NUM_ROWS_PER_SECTION = 5;
  31. let pageIndex = 0;
  32. class Demo extends React.Component {
  33. constructor(props) {
  34. super(props);
  35. const getSectionData = (dataBlob, sectionID) => dataBlob[sectionID];
  36. const getRowData = (dataBlob, sectionID, rowID) => dataBlob[rowID];
  37. const dataSource = new ListView.DataSource({
  38. getRowData,
  39. getSectionHeaderData: getSectionData,
  40. rowHasChanged: (row1, row2) => row1 !== row2,
  41. sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
  42. });
  43. this.dataBlob = {};
  44. this.sectionIDs = [];
  45. this.rowIDs = [];
  46. this.genData = (pIndex = 0) => {
  47. for (let i = 0; i < NUM_SECTIONS; i++) {
  48. const ii = (pIndex * NUM_SECTIONS) + i;
  49. const sectionName = `Section ${ii}`;
  50. this.sectionIDs.push(sectionName);
  51. this.dataBlob[sectionName] = sectionName;
  52. this.rowIDs[ii] = [];
  53. for (let jj = 0; jj < NUM_ROWS_PER_SECTION; jj++) {
  54. const rowName = `S${ii}, R${jj}`;
  55. this.rowIDs[ii].push(rowName);
  56. this.dataBlob[rowName] = rowName;
  57. }
  58. }
  59. // new object ref
  60. this.sectionIDs = [].concat(this.sectionIDs);
  61. this.rowIDs = [].concat(this.rowIDs);
  62. };
  63. this.state = {
  64. dataSource: dataSource.cloneWithRowsAndSections(this.dataBlob, this.sectionIDs, this.rowIDs),
  65. isLoading: true,
  66. };
  67. }
  68. componentDidMount() {
  69. // you can scroll to the specified position
  70. // setTimeout(() => this.refs.lv.refs.listview.scrollTo(0, 120), 800); // also work
  71. // setTimeout(() => this.refs.lv.scrollTo(0, 120), 800); // recommend usage
  72. // simulate initial Ajax
  73. setTimeout(() => {
  74. this.genData();
  75. this.setState({
  76. dataSource: this.state.dataSource.cloneWithRowsAndSections(this.dataBlob, this.sectionIDs, this.rowIDs),
  77. isLoading: false,
  78. });
  79. }, 600);
  80. }
  81. // If you use redux, the data maybe at props, you need use `componentWillReceiveProps`
  82. // componentWillReceiveProps(nextProps) {
  83. // if (nextProps.dataSource !== this.props.dataSource) {
  84. // this.setState({
  85. // dataSource: this.state.dataSource.cloneWithRowsAndSections(nextProps.dataSource),
  86. // });
  87. // }
  88. // }
  89. onEndReached = (event) => {
  90. // load new data
  91. // hasMore: from backend data, indicates whether it is the last page, here is false
  92. if (this.state.isLoading && !this.state.hasMore) {
  93. return;
  94. }
  95. console.log('reach end', event);
  96. this.setState({ isLoading: true });
  97. setTimeout(() => {
  98. this.genData(++pageIndex);
  99. this.setState({
  100. dataSource: this.state.dataSource.cloneWithRowsAndSections(this.dataBlob, this.sectionIDs, this.rowIDs),
  101. isLoading: false,
  102. });
  103. }, 1000);
  104. }
  105. render() {
  106. const separator = (sectionID, rowID) => (
  107. <div key={`${sectionID}-${rowID}`}
  108. style={{
  109. backgroundColor: '#F5F5F9',
  110. height: 8,
  111. borderTop: '1px solid #ECECED',
  112. borderBottom: '1px solid #ECECED',
  113. }}
  114. />
  115. );
  116. const row = (rowData, sectionID, rowID) => {
  117. if (index < 0) {
  118. index = data.length - 1;
  119. }
  120. const obj = data[index--];
  121. return (
  122. <div key={rowID} className="row">
  123. <div className="row-title">{obj.title}</div>
  124. <div style={{ display: '-webkit-box', display: 'flex', padding: '0.3rem 0' }}>
  125. <img style={{ height: '1.28rem', marginRight: '0.3rem' }} src={obj.img} alt="icon" />
  126. <div className="row-text">
  127. <div style={{ marginBottom: '0.16rem', fontWeight: 'bold' }}>{obj.des}</div>
  128. <div><span style={{ fontSize: '0.6rem', color: '#FF6E27' }}>35</span>¥</div>
  129. </div>
  130. </div>
  131. </div>
  132. );
  133. };
  134. return (<div style={{ margin: '0 auto', width: '96%' }}>
  135. <ListView ref="lv"
  136. dataSource={this.state.dataSource}
  137. renderHeader={() => <span>header</span>}
  138. renderFooter={() => (<div style={{ padding: 30, textAlign: 'center' }}>
  139. {this.state.isLoading ? 'Loading...' : 'Loaded'}
  140. </div>)}
  141. renderSectionHeader={sectionData => (
  142. <div>{`Task ${sectionData.split(' ')[1]}`}</div>
  143. )}
  144. renderBodyComponent={() => <MyBody />}
  145. renderRow={row}
  146. renderSeparator={separator}
  147. className="fortest"
  148. style={{
  149. height: document.documentElement.clientHeight * 3 / 4,
  150. overflow: 'auto',
  151. border: '1px solid #ddd',
  152. margin: '0.1rem 0',
  153. }}
  154. pageSize={4}
  155. onScroll={() => { console.log('scroll'); }}
  156. scrollRenderAheadDistance={500}
  157. scrollEventThrottle={200}
  158. onEndReached={this.onEndReached}
  159. onEndReachedThreshold={10}
  160. />
  161. </div>);
  162. }
  163. }
  164. ReactDOM.render(<Demo />, mountNode);
  1. .row {
  2. padding: 0 0.3rem;
  3. background-color: white;
  4. }
  5. .row-title {
  6. height: 1rem;
  7. line-height: 1rem;
  8. color: #888;
  9. font-size: 0.36rem;
  10. border-bottom: 1px solid #F6F6F6;
  11. }
  12. .row-text {
  13. display: inline-block;
  14. font-size: 0.32rem;
  15. line-height: 1;
  16. }

body 容器

use html body as a scroll container.

  1. /* eslint no-dupe-keys: 0 */
  2. import { ListView } from 'antd-mobile';
  3. const data = [
  4. {
  5. img: 'https://zos.alipayobjects.com/rmsportal/dKbkpPXKfvZzWCM.png',
  6. title: 'Meet hotel',
  7. des: '不是所有的兼职汪都需要风吹日晒',
  8. },
  9. {
  10. img: 'https://zos.alipayobjects.com/rmsportal/XmwCzSeJiqpkuMB.png',
  11. title: 'McDonald\'s invites you',
  12. des: '不是所有的兼职汪都需要风吹日晒',
  13. },
  14. {
  15. img: 'https://zos.alipayobjects.com/rmsportal/hfVtzEhPzTUewPm.png',
  16. title: 'Eat the week',
  17. des: '不是所有的兼职汪都需要风吹日晒',
  18. },
  19. ];
  20. let index = data.length - 1;
  21. const NUM_ROWS = 20;
  22. let pageIndex = 0;
  23. class Demo extends React.Component {
  24. constructor(props) {
  25. super(props);
  26. const dataSource = new ListView.DataSource({
  27. rowHasChanged: (row1, row2) => row1 !== row2,
  28. });
  29. this.genData = (pIndex = 0) => {
  30. const dataBlob = {};
  31. for (let i = 0; i < NUM_ROWS; i++) {
  32. const ii = (pIndex * NUM_ROWS) + i;
  33. dataBlob[`${ii}`] = `row - ${ii}`;
  34. }
  35. return dataBlob;
  36. };
  37. this.state = {
  38. dataSource: dataSource.cloneWithRows({}),
  39. isLoading: true,
  40. };
  41. }
  42. componentDidMount() {
  43. // you can scroll to the specified position
  44. // setTimeout(() => this.refs.lv.refs.listview.scrollTo(0, 200), 800); // also work
  45. // setTimeout(() => this.refs.lv.scrollTo(0, 200), 800); // recommend usage
  46. // simulate initial Ajax
  47. setTimeout(() => {
  48. this.rData = this.genData();
  49. this.setState({
  50. dataSource: this.state.dataSource.cloneWithRows(this.rData),
  51. isLoading: false,
  52. });
  53. }, 600);
  54. }
  55. // If you use redux, the data maybe at props, you need use `componentWillReceiveProps`
  56. // componentWillReceiveProps(nextProps) {
  57. // if (nextProps.dataSource !== this.props.dataSource) {
  58. // this.setState({
  59. // dataSource: this.state.dataSource.cloneWithRows(nextProps.dataSource),
  60. // });
  61. // }
  62. // }
  63. onEndReached = (event) => {
  64. // load new data
  65. // hasMore: from backend data, indicates whether it is the last page, here is false
  66. if (this.state.isLoading && !this.state.hasMore) {
  67. return;
  68. }
  69. console.log('reach end', event);
  70. this.setState({ isLoading: true });
  71. setTimeout(() => {
  72. this.rData = { ...this.rData, ...this.genData(++pageIndex) };
  73. this.setState({
  74. dataSource: this.state.dataSource.cloneWithRows(this.rData),
  75. isLoading: false,
  76. });
  77. }, 1000);
  78. }
  79. render() {
  80. const separator = (sectionID, rowID) => (
  81. <div key={`${sectionID}-${rowID}`}
  82. style={{
  83. backgroundColor: '#F5F5F9',
  84. height: 8,
  85. borderTop: '1px solid #ECECED',
  86. borderBottom: '1px solid #ECECED',
  87. }}
  88. />
  89. );
  90. const row = (rowData, sectionID, rowID) => {
  91. if (index < 0) {
  92. index = data.length - 1;
  93. }
  94. const obj = data[index--];
  95. return (
  96. <div key={rowID} className="row">
  97. <div className="row-title">{obj.title}</div>
  98. <div style={{ display: '-webkit-box', display: 'flex', padding: '0.3rem 0' }}>
  99. <img style={{ height: '1.28rem', marginRight: '0.3rem' }} src={obj.img} alt="icon" />
  100. <div className="row-text">
  101. <div style={{ marginBottom: '0.16rem', fontWeight: 'bold' }}>{obj.des}</div>
  102. <div><span style={{ fontSize: '0.6rem', color: '#FF6E27' }}>{rowID}</span>¥</div>
  103. </div>
  104. </div>
  105. </div>
  106. );
  107. };
  108. return (
  109. <ListView ref="lv"
  110. dataSource={this.state.dataSource}
  111. renderHeader={() => <span>header</span>}
  112. renderFooter={() => (<div style={{ padding: 30, textAlign: 'center' }}>
  113. {this.state.isLoading ? 'Loading...' : 'Loaded'}
  114. </div>)}
  115. renderRow={row}
  116. renderSeparator={separator}
  117. className="am-list"
  118. pageSize={4}
  119. useBodyScroll
  120. onScroll={() => { console.log('scroll'); }}
  121. scrollRenderAheadDistance={500}
  122. scrollEventThrottle={200}
  123. onEndReached={this.onEndReached}
  124. onEndReachedThreshold={10}
  125. />
  126. );
  127. }
  128. }
  129. ReactDOM.render(<Demo />, mountNode);
  1. .row {
  2. padding: 0 0.3rem;
  3. background-color: white;
  4. }
  5. .row-title {
  6. height: 1rem;
  7. line-height: 1rem;
  8. color: #888;
  9. font-size: 0.36rem;
  10. border-bottom: 1px solid #F6F6F6;
  11. }
  12. .row-text {
  13. display: inline-block;
  14. font-size: 0.32rem;
  15. line-height: 1;
  16. }

标题吸顶(body 容器)

sticky block header to the top of the page

  1. /* eslint no-dupe-keys: 0 */
  2. import { ListView } from 'antd-mobile';
  3. const data = [
  4. {
  5. img: 'https://zos.alipayobjects.com/rmsportal/dKbkpPXKfvZzWCM.png',
  6. title: 'Meet hotel',
  7. des: '不是所有的兼职汪都需要风吹日晒',
  8. },
  9. {
  10. img: 'https://zos.alipayobjects.com/rmsportal/XmwCzSeJiqpkuMB.png',
  11. title: 'McDonald\'s invites you',
  12. des: '不是所有的兼职汪都需要风吹日晒',
  13. },
  14. {
  15. img: 'https://zos.alipayobjects.com/rmsportal/hfVtzEhPzTUewPm.png',
  16. title: 'Eat the week',
  17. des: '不是所有的兼职汪都需要风吹日晒',
  18. },
  19. ];
  20. let index = data.length - 1;
  21. const NUM_SECTIONS = 5;
  22. const NUM_ROWS_PER_SECTION = 5;
  23. let pageIndex = 0;
  24. class Demo extends React.Component {
  25. constructor(props) {
  26. super(props);
  27. const getSectionData = (dataBlob, sectionID) => dataBlob[sectionID];
  28. const getRowData = (dataBlob, sectionID, rowID) => dataBlob[rowID];
  29. const dataSource = new ListView.DataSource({
  30. getRowData,
  31. getSectionHeaderData: getSectionData,
  32. rowHasChanged: (row1, row2) => row1 !== row2,
  33. sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
  34. });
  35. this.dataBlob = {};
  36. this.sectionIDs = [];
  37. this.rowIDs = [];
  38. this.genData = (pIndex = 0) => {
  39. for (let i = 0; i < NUM_SECTIONS; i++) {
  40. const ii = (pIndex * NUM_SECTIONS) + i;
  41. const sectionName = `Section ${ii}`;
  42. this.sectionIDs.push(sectionName);
  43. this.dataBlob[sectionName] = sectionName;
  44. this.rowIDs[ii] = [];
  45. for (let jj = 0; jj < NUM_ROWS_PER_SECTION; jj++) {
  46. const rowName = `S${ii}, R${jj}`;
  47. this.rowIDs[ii].push(rowName);
  48. this.dataBlob[rowName] = rowName;
  49. }
  50. }
  51. // new object ref
  52. this.sectionIDs = [].concat(this.sectionIDs);
  53. this.rowIDs = [].concat(this.rowIDs);
  54. };
  55. this.state = {
  56. dataSource: dataSource.cloneWithRowsAndSections(this.dataBlob, this.sectionIDs, this.rowIDs),
  57. isLoading: true,
  58. };
  59. }
  60. componentDidMount() {
  61. // you can scroll to the specified position
  62. // setTimeout(() => this.refs.lv.refs.listview.scrollTo(0, 200), 800); // also work
  63. // setTimeout(() => this.refs.lv.scrollTo(0, 200), 800); // recommend usage
  64. // simulate initial Ajax
  65. setTimeout(() => {
  66. this.genData();
  67. this.setState({
  68. dataSource: this.state.dataSource.cloneWithRowsAndSections(this.dataBlob, this.sectionIDs, this.rowIDs),
  69. isLoading: false,
  70. });
  71. }, 600);
  72. }
  73. // If you use redux, the data maybe at props, you need use `componentWillReceiveProps`
  74. // componentWillReceiveProps(nextProps) {
  75. // if (nextProps.dataSource !== this.props.dataSource) {
  76. // this.setState({
  77. // dataSource: this.state.dataSource.cloneWithRowsAndSections(nextProps.dataSource),
  78. // });
  79. // }
  80. // }
  81. onEndReached = (event) => {
  82. // load new data
  83. // hasMore: from backend data, indicates whether it is the last page, here is false
  84. if (this.state.isLoading && !this.state.hasMore) {
  85. return;
  86. }
  87. console.log('reach end', event);
  88. this.setState({ isLoading: true });
  89. setTimeout(() => {
  90. this.genData(++pageIndex);
  91. this.setState({
  92. dataSource: this.state.dataSource.cloneWithRowsAndSections(this.dataBlob, this.sectionIDs, this.rowIDs),
  93. isLoading: false,
  94. });
  95. }, 1000);
  96. }
  97. render() {
  98. const separator = (sectionID, rowID) => (
  99. <div key={`${sectionID}-${rowID}`}
  100. style={{
  101. backgroundColor: '#F5F5F9',
  102. height: 8,
  103. borderTop: '1px solid #ECECED',
  104. borderBottom: '1px solid #ECECED',
  105. }}
  106. />
  107. );
  108. const row = (rowData, sectionID, rowID) => {
  109. if (index < 0) {
  110. index = data.length - 1;
  111. }
  112. const obj = data[index--];
  113. return (
  114. <div key={rowID} className="row">
  115. <div className="row-title">{obj.title}</div>
  116. <div style={{ display: '-webkit-box', display: 'flex', padding: '0.3rem 0' }}>
  117. <img style={{ height: '1.28rem', marginRight: '0.3rem' }} src={obj.img} alt="icon" />
  118. <div className="row-text">
  119. <div style={{ marginBottom: '0.16rem', fontWeight: 'bold' }}>{obj.des}</div>
  120. <div><span style={{ fontSize: '0.6rem', color: '#FF6E27' }}>35</span>¥</div>
  121. </div>
  122. </div>
  123. </div>
  124. );
  125. };
  126. return (
  127. <ListView ref="lv"
  128. dataSource={this.state.dataSource}
  129. renderHeader={() => <span>header</span>}
  130. renderFooter={() => (<div style={{ padding: 30, textAlign: 'center' }}>
  131. {this.state.isLoading ? 'Loading...' : 'Loaded'}
  132. </div>)}
  133. renderSectionHeader={sectionData => (
  134. <div>{`Task ${sectionData.split(' ')[1]}`}</div>
  135. )}
  136. renderRow={row}
  137. renderSeparator={separator}
  138. className="am-list"
  139. pageSize={4}
  140. onScroll={() => { console.log('scroll'); }}
  141. scrollEventThrottle={200}
  142. onEndReached={this.onEndReached}
  143. onEndReachedThreshold={10}
  144. stickyHeader
  145. stickyProps={{
  146. stickyStyle: { zIndex: 999, WebkitTransform: 'none', transform: 'none' },
  147. // topOffset: -43,
  148. // isActive: false,
  149. }}
  150. stickyContainerProps={{
  151. className: 'for-stickyContainer-demo',
  152. }}
  153. />
  154. );
  155. }
  156. }
  157. ReactDOM.render(<Demo />, mountNode);
  1. .row {
  2. padding: 0 0.3rem;
  3. background-color: white;
  4. }
  5. .row-title {
  6. height: 1rem;
  7. line-height: 1rem;
  8. color: #888;
  9. font-size: 0.36rem;
  10. border-bottom: 1px solid #F6F6F6;
  11. }
  12. .row-text {
  13. display: inline-block;
  14. font-size: 0.32rem;
  15. line-height: 1;
  16. }

索引列表

Index List

  1. /* eslint no-mixed-operators: 0 */
  2. import { province } from 'antd-mobile-demo-data';
  3. import { ListView, List } from 'antd-mobile';
  4. const { Item } = List;
  5. class Demo extends React.Component {
  6. constructor(props) {
  7. super(props);
  8. const getSectionData = (dataBlob, sectionID) => dataBlob[sectionID];
  9. const getRowData = (dataBlob, sectionID, rowID) => dataBlob[rowID];
  10. const dataSource = new ListView.DataSource({
  11. getRowData,
  12. getSectionHeaderData: getSectionData,
  13. rowHasChanged: (row1, row2) => row1 !== row2,
  14. sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
  15. });
  16. const dataBlob = {};
  17. const sectionIDs = [];
  18. const rowIDs = [];
  19. Object.keys(province).forEach((item, index) => {
  20. sectionIDs.push(item);
  21. dataBlob[item] = item;
  22. rowIDs[index] = [];
  23. province[item].forEach((jj) => {
  24. rowIDs[index].push(jj.value);
  25. dataBlob[jj.value] = jj.label;
  26. });
  27. });
  28. this.state = {
  29. dataSource: dataSource.cloneWithRowsAndSections(dataBlob, sectionIDs, rowIDs),
  30. headerPressCount: 0,
  31. };
  32. }
  33. render() {
  34. return (
  35. <ListView.IndexedList
  36. dataSource={this.state.dataSource}
  37. renderHeader={() => <span>custom header</span>}
  38. renderFooter={() => <span>custom footer</span>}
  39. renderSectionHeader={sectionData => (<div className="ih">{sectionData}</div>)}
  40. renderRow={rowData => (<Item>{rowData}</Item>)}
  41. className="fortest"
  42. style={{
  43. height: document.documentElement.clientHeight * 3 / 4,
  44. overflow: 'auto',
  45. }}
  46. quickSearchBarStyle={{
  47. position: 'absolute',
  48. top: 20,
  49. }}
  50. delayTime={10}
  51. delayActivityIndicator={<div style={{ padding: 25, textAlign: 'center' }}>rendering...</div>}
  52. />
  53. );
  54. }
  55. }
  56. ReactDOM.render(<Demo />, mountNode);

索引列表(标题吸顶)

sticky index List

  1. import { province as provinceData } from 'antd-mobile-demo-data';
  2. import { ListView, List, SearchBar } from 'antd-mobile';
  3. const { Item } = List;
  4. class Demo extends React.Component {
  5. constructor(props) {
  6. super(props);
  7. const getSectionData = (dataBlob, sectionID) => dataBlob[sectionID];
  8. const getRowData = (dataBlob, sectionID, rowID) => dataBlob[rowID];
  9. const dataSource = new ListView.DataSource({
  10. getRowData,
  11. getSectionHeaderData: getSectionData,
  12. rowHasChanged: (row1, row2) => row1 !== row2,
  13. sectionHeaderHasChanged: (s1, s2) => s1 !== s2,
  14. });
  15. this.createDs = (ds, province) => {
  16. const dataBlob = {};
  17. const sectionIDs = [];
  18. const rowIDs = [];
  19. Object.keys(province).forEach((item, index) => {
  20. sectionIDs.push(item);
  21. dataBlob[item] = item;
  22. rowIDs[index] = [];
  23. province[item].forEach((jj) => {
  24. rowIDs[index].push(jj.value);
  25. dataBlob[jj.value] = jj.label;
  26. });
  27. });
  28. return ds.cloneWithRowsAndSections(dataBlob, sectionIDs, rowIDs);
  29. };
  30. this.state = {
  31. inputValue: '',
  32. dataSource: this.createDs(dataSource, provinceData),
  33. headerPressCount: 0,
  34. };
  35. }
  36. onSearch = (val) => {
  37. const pd = { ...provinceData };
  38. Object.keys(pd).forEach((item) => {
  39. pd[item] = pd[item].filter(jj => jj.spell.toLocaleLowerCase().indexOf(val) > -1);
  40. });
  41. this.setState({
  42. inputValue: val,
  43. dataSource: this.createDs(this.state.dataSource, pd),
  44. });
  45. }
  46. render() {
  47. return (<div style={{ paddingTop: '0.88rem', position: 'relative' }}>
  48. <div style={{ position: 'absolute', top: 0, left: 0, right: 0 }}>
  49. <SearchBar
  50. value={this.state.inputValue}
  51. placeholder="Search"
  52. onChange={this.onSearch}
  53. onClear={() => { console.log('onClear'); }}
  54. onCancel={() => { console.log('onCancel'); }}
  55. />
  56. </div>
  57. <ListView.IndexedList
  58. dataSource={this.state.dataSource}
  59. renderHeader={() => <span>custom header</span>}
  60. renderFooter={() => <span>custom footer</span>}
  61. renderSectionHeader={sectionData => (<div className="ih">{sectionData}</div>)}
  62. renderRow={rowData => (<Item>{rowData}</Item>)}
  63. className="am-list"
  64. stickyHeader
  65. stickyProps={{
  66. stickyStyle: { zIndex: 999 },
  67. }}
  68. quickSearchBarStyle={{
  69. top: 85,
  70. }}
  71. delayTime={10}
  72. delayActivityIndicator={<div style={{ padding: 25, textAlign: 'center' }}>rendering...</div>}
  73. />
  74. </div>);
  75. }
  76. }
  77. ReactDOM.render(<Demo />, mountNode);

ListView长列表 - 图1

API

适用平台:WEB、React-Native(DEPRECATED) 注意: React Native ListView 现在已经被标记了DEPRECATED。因为我们内部是直接从 'react-native' 里导入ListView,所以我们也将废弃 ListView RN 版本。
属性说明类型默认值
dataSourceListView.DataSource (cn) / ListView.DataSource (us) 实例ListViewDataSource-
initialListSize指定在组件刚挂载的时候渲染多少行数据,用这个属性来确保首屏显示合适数量的数据number-
onEndReached当所有的数据都已经渲染过,并且列表被滚动到距离最底部不足onEndReachedThreshold个像素的距离时调用(event?) => {}-
onEndReachedThreshold调用onEndReached之前的临界值,单位是像素number1000
pageSize每次事件循环(每帧)渲染的行数number1
renderHeader / renderFooter页头与页脚(如果提供)会在每次渲染过程中都重新渲染。如果它们重绘的性能开销很大,把他们包装到一个StaticContainer或者其它恰当的结构中。页脚在列表的最底部,而页头会在最顶部() => renderable-
renderRow从数据源(data source)中接受一条数据,以及它和它所在section的ID。返回一个可渲染的组件来为这行数据进行渲染。默认情况下参数中的数据就是放进数据源中的数据本身,不过也可以提供一些转换器。如果某一行正在被高亮(通过调用highlightRow函数),ListView会得到相应的通知。(rowData, sectionID, rowID, highlightRow) => renderable-
renderScrollComponent指定一个函数,在其中返回一个可以滚动的组件,ListView将会在该组件内部进行渲染。默认情况下会返回一个包含指定属性的ScrollView。(props) => renderable-
renderSectionHeader如果提供了此函数,会为每个小节(section)渲染一个标题(sectionData, sectionID) => renderable-
renderSeparator如果提供了此属性,一个可渲染的组件会被渲染在每一行下面,除了小节标题的前面的最后一行。在其上方的小节ID和行ID,以及邻近的行是否被高亮会作为参数传递进来。(sectionID, rowID, adjacentRowHighlighted) => renderable-
scrollRenderAheadDistance当一个行接近屏幕范围多少像素之内的时候,就开始渲染这一行number1000
contentContainerStyle这些样式会应用到一个内层的内容容器上,所有的子视图都会包裹在内容容器内Object-
horizontal当此属性为true的时候,所有的的子视图会在水平方向上排成一行,而不是默认的在垂直方向上排成一列boolfalse
onContentSizeChange此函数会在 ScrollView 内部可滚动内容的视图发生变化时调用。(contentWidth, contentHeight) => {}-
onScroll在滚动的过程中,每帧最多调用一次此回调函数。调用的频率可以用scrollEventThrottle属性来控制。e => {}-
scrollEventThrottle控制在滚动过程中,scroll事件被调用的频率number50
refreshControl指定 RefreshControl 组件,用于为ScrollView 提供下拉刷新功能。element-
onLayout当组件挂载或者布局变化的时候调用({nativeEvent:{ layout:{ width, height }}}) => {}-
——
renderBodyComponent (web only)自定义 body 的包裹组件() => renderable-
renderSectionBodyWrapper (web only)渲染自定义的区块包裹组件(sectionID) => renderable-
useBodyScroll (web only)使用 html 的 body 作为滚动容器boolfalse
useZscroller (web only)使用 zscroller 来模拟实现滚动容器 (可用于一些低端 Android 机上),注意:开启后useBodyScrollstickyHeader设置会自动被忽略boolfalse
scrollerOptions (web only)zscroller optionsObject-
stickyHeader (web only)固定区块标题到页面顶部 (注意: 设置后会自动开启useBodyScroll),启用后还可以设置 stickyProps / stickyContainerProps (详见 react-sticky)boolfalse

方法

  • getMetrics() - 导出一些用于性能分析的数据。

  • scrollTo(…args) - 滚动到指定的x, y偏移处(暂不支持过渡动画)。

ListView.IndexedList

适用平台:WEB。 此组件常用于 “通讯录”/“城市列表” 等场景中,支持索引导航功能。

你可以使用 ListView 上的几乎所有 APIs,除了useZscroller

注意:由于索引列表可以点击任一项索引来定位其内容、即内容需要直接滚动到任意位置,这样就难以做到像 ListView 一样能在滚动时自动懒渲染。目前实现上只支持分两步渲染,能借此达到首屏优先显示目的,但如果列表数据量过大时、整体性能仍会有影响。

属性说明类型默认值
quickSearchBarTop快捷导航栏最顶部按钮、常用于回到顶部object{value:string, label:string}{ value: '#', label: '#' }
quickSearchBarStylequickSearchBar 的 styleobject-
onQuickSearch快捷导航切换时调用(sectionID: any, topId?:any) => void-
showQuickSearchIndicatorwhether show quick search indicatorboolfalse
delayTime延迟渲染时间设置(用于首屏优化,一开始渲染initialListSize数量的数据,在此时间后、延迟渲染剩余的数据项、即totalRowCount - initialListSizenumber100ms
delayActivityIndicator延迟渲染的 loading 指示器react node-

提示

ListView 有三种类型的滚动容器:
  • 局部 div 容器

    • 默认,注意:需要手动给 ListView 设置高度
  • html 的 body 容器

    • 设置useBodyScrollstickyHeader后生效 (不需要设置高度)
  • 使用 zscroller 的模拟滚动容器

    • 设置useZscroller后生效,然后可以设置scrollerOptions (需要手动给 ListView 设置高度)
对 dataSource 对象变化时的处理方式是什么?何时调用 onEndReached 方法? ListView 在 componentWillReceiveProps 里会监听 dataSource 对象的变化,并做一次this._renderMoreRowsIfNeeded() ,由于此时this.state.curRenderedRowsCount === this.props.dataSource.getRowCount()即已经渲染的数据与 dataSource 里已有的数据项个数相同,所以 ListView 认为应该再调用 onEndReached 方法。 onEndReached 为什么会不停调用?520#issuecomment-263510596其他问题:#633#573#541