RefreshControl 下拉刷新

通过触发,立刻重新加载内容。

规则

  • 只能和 ListView 结合使用,不能单独使用。

  • 可考虑定期自动刷新, e.g. 登录 APP 后,自动刷新首页 List。

代码演示

ListView 下拉刷新

下拉刷新

  1. /* eslint no-dupe-keys: 0, no-mixed-operators: 0 */
  2. import { RefreshControl, 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. function genData(pIndex = 0) {
  24. const dataArr = [];
  25. for (let i = 0; i < NUM_ROWS; i++) {
  26. dataArr.push(`row - ${(pIndex * NUM_ROWS) + i}`);
  27. }
  28. return dataArr;
  29. }
  30. class App extends React.Component {
  31. constructor(props) {
  32. super(props);
  33. const dataSource = new ListView.DataSource({
  34. rowHasChanged: (row1, row2) => row1 !== row2,
  35. });
  36. this.state = {
  37. dataSource,
  38. refreshing: true,
  39. height: document.documentElement.clientHeight,
  40. };
  41. }
  42. // If you use redux, the data maybe at props, you need use `componentWillReceiveProps`
  43. // componentWillReceiveProps(nextProps) {
  44. // if (nextProps.dataSource !== this.props.dataSource) {
  45. // this.setState({
  46. // dataSource: this.state.dataSource.cloneWithRows(nextProps.dataSource),
  47. // });
  48. // }
  49. // }
  50. componentDidMount() {
  51. // Set the appropriate height
  52. setTimeout(() => this.setState({
  53. height: this.state.height - ReactDOM.findDOMNode(this.lv).offsetTop,
  54. }), 0);
  55. // handle https://github.com/ant-design/ant-design-mobile/issues/1588
  56. this.lv.getInnerViewNode().addEventListener('touchstart', this.ts = (e) => {
  57. this.tsPageY = e.touches[0].pageY;
  58. });
  59. // In chrome61 `document.body.scrollTop` is invalid
  60. const scrollNode = document.scrollingElement ? document.scrollingElement : document.body;
  61. this.lv.getInnerViewNode().addEventListener('touchmove', this.tm = (e) => {
  62. this.tmPageY = e.touches[0].pageY;
  63. if (this.tmPageY > this.tsPageY && this.scrollerTop <= 0 && scrollNode.scrollTop > 0) {
  64. console.log('start pull to refresh');
  65. this.domScroller.options.preventDefaultOnTouchMove = false;
  66. } else {
  67. this.domScroller.options.preventDefaultOnTouchMove = undefined;
  68. }
  69. });
  70. }
  71. componentWillUnmount() {
  72. this.lv.getInnerViewNode().removeEventListener('touchstart', this.ts);
  73. this.lv.getInnerViewNode().removeEventListener('touchmove', this.tm);
  74. }
  75. onScroll = (e) => {
  76. this.scrollerTop = e.scroller.getValues().top;
  77. this.domScroller = e;
  78. };
  79. onRefresh = () => {
  80. console.log('onRefresh');
  81. if (!this.manuallyRefresh) {
  82. this.setState({ refreshing: true });
  83. } else {
  84. this.manuallyRefresh = false;
  85. }
  86. // simulate initial Ajax
  87. setTimeout(() => {
  88. this.rData = genData();
  89. this.setState({
  90. dataSource: this.state.dataSource.cloneWithRows(this.rData),
  91. refreshing: false,
  92. showFinishTxt: true,
  93. });
  94. if (this.domScroller) {
  95. this.domScroller.scroller.options.animationDuration = 500;
  96. }
  97. }, 600);
  98. };
  99. onEndReached = (event) => {
  100. // load new data
  101. // hasMore: from backend data, indicates whether it is the last page, here is false
  102. if (this.state.isLoading && !this.state.hasMore) {
  103. return;
  104. }
  105. console.log('reach end', event);
  106. this.setState({ isLoading: true });
  107. setTimeout(() => {
  108. this.rData = [...this.rData, ...genData(++pageIndex)];
  109. this.setState({
  110. dataSource: this.state.dataSource.cloneWithRows(this.rData),
  111. isLoading: false,
  112. });
  113. }, 1000);
  114. };
  115. scrollingComplete = () => {
  116. // In general, this.scrollerTop should be 0 at the end, but it may be -0.000051 in chrome61.
  117. if (this.scrollerTop >= -1) {
  118. this.setState({ showFinishTxt: false });
  119. }
  120. }
  121. renderCustomIcon() {
  122. return [
  123. <div key="0" className="am-refresh-control-pull">
  124. <span>{this.state.showFinishTxt ? '刷新完毕' : '下拉可以刷新'}</span>
  125. </div>,
  126. <div key="1" className="am-refresh-control-release">
  127. <span>松开立即刷新</span>
  128. </div>,
  129. ];
  130. }
  131. render() {
  132. const separator = (sectionID, rowID) => (
  133. <div
  134. key={`${sectionID}-${rowID}`}
  135. style={{
  136. backgroundColor: '#F5F5F9',
  137. height: 8,
  138. borderTop: '1px solid #ECECED',
  139. borderBottom: '1px solid #ECECED',
  140. }}
  141. />
  142. );
  143. const row = (rowData, sectionID, rowID) => {
  144. if (index < 0) {
  145. index = data.length - 1;
  146. }
  147. const obj = data[index--];
  148. return (
  149. <div key={rowID}
  150. style={{
  151. padding: '0 0.3rem',
  152. backgroundColor: 'white',
  153. }}
  154. >
  155. <div style={{ height: '1rem', lineHeight: '1rem', color: '#888', fontSize: '0.36rem', borderBottom: '1px solid #ddd' }}>
  156. {obj.title}
  157. </div>
  158. <div style={{ display: '-webkit-box', display: 'flex', padding: '0.3rem' }}>
  159. <img style={{ height: '1.26rem', width: '1.26rem', marginRight: '0.3rem' }} src={obj.img} alt="icon" />
  160. <div style={{ display: 'inline-block' }}>
  161. <div style={{ marginBottom: '0.16rem', color: '#000', fontSize: '0.32rem', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap', maxWidth: '5rem' }}>{obj.des}-{rowData}</div>
  162. <div style={{ fontSize: '0.32rem' }}><span style={{ fontSize: '0.6rem', color: '#FF6E27' }}>{rowID}</span> 元/任务</div>
  163. </div>
  164. </div>
  165. </div>
  166. );
  167. };
  168. return (
  169. <ListView
  170. ref={el => this.lv = el}
  171. dataSource={this.state.dataSource}
  172. renderHeader={() => <span>Pull to refresh</span>}
  173. renderFooter={() => (<div style={{ padding: '0.3rem', textAlign: 'center' }}>
  174. {this.state.isLoading ? 'Loading...' : 'Loaded'}
  175. </div>)}
  176. renderRow={row}
  177. renderSeparator={separator}
  178. initialListSize={5}
  179. pageSize={5}
  180. style={{
  181. height: this.state.height,
  182. border: '1px solid #ddd',
  183. margin: '0.1rem 0',
  184. }}
  185. scrollerOptions={{ scrollbars: true, scrollingComplete: this.scrollingComplete }}
  186. refreshControl={<RefreshControl
  187. refreshing={this.state.refreshing}
  188. onRefresh={this.onRefresh}
  189. icon={this.renderCustomIcon()}
  190. />}
  191. onScroll={this.onScroll}
  192. scrollRenderAheadDistance={200}
  193. scrollEventThrottle={20}
  194. onEndReached={this.onEndReached}
  195. onEndReachedThreshold={10}
  196. />
  197. );
  198. }
  199. }
  200. ReactDOM.render(<App />, mountNode);
  1. .am-refresh-control-deactive .am-refresh-control-ptr-icon {
  2. display: block;
  3. }

RefreshControl下拉刷新 - 图1

API (web)

注意: RefreshControl 会自动使用 ListView 的useZscroller(参考 ListView 代码),所以需要给 ListView 设置固定的高度

属性说明类型默认值
icon刷新指示icon, 包含 pull and release 状态react node-
loading加载指示器react node-
distanceToRefresh刷新距离number50 / 2 * (window.devicePixelRatio || 2)
onRefreshrequired, 刷新回调函数() => void-
refreshing是否显示刷新状态boolfalse
如何自己设置icon/loading,参考 https://github.com/ant-design/ant-design-mobile/blob/1.x/components/refresh-control/index.web.tsx#L10## API (react-native)#https://facebook.github.io/react-native/docs/refreshcontrol.html#props