├── README.md
├── RefreshableListView.js
├── TopButton.js
├── demo.gif
├── img
├── va-top.png
├── va-top@2x.png
└── va-top@3x.png
└── package.json
/README.md:
--------------------------------------------------------------------------------
1 | # react-native-sk-refreshable-listview
2 |
3 | ##What is it
4 |
5 | react-native-sk-refreshable-listview is a component wraps ListView, supports: 1 pull down to refresh 2 pull up to load more 3 scroll to top 4 scroll to bottom
6 |
7 | ##How to use it
8 |
9 | 1. `npm install react-native-sk-refreshable-listview@latest --save`
10 |
11 | 2. Write this in index.ios.js / index.android.js
12 |
13 | ```javascript
14 |
15 | 'use strict';
16 | import React, {
17 | AppRegistry,
18 | StyleSheet,
19 | Text,
20 | View,
21 | Dimensions,
22 | } from 'react-native';
23 |
24 |
25 | var RefreshableListView = require('react-native-sk-refreshable-listview');
26 | var {width, height} = Dimensions.get('window');
27 | var dataUrl = 'https://raw.githubusercontent.com/shigebeyond/react-native-sk-refreshable-listview/master/test.json';
28 | var data = [
29 | {id: 1, text: 'row 1'},
30 | {id: 2, text: 'row 2'},
31 | {id: 3, text: 'row 3'},
32 | {id: 4, text: 'row 4'},
33 | {id: 5, text: 'row 5'},
34 | {id: 6, text: 'row 6'},
35 | {id: 7, text: 'row 7'},
36 | {id: 8, text: 'row 8'},
37 | {id: 9, text: 'row 9'},
38 | {id: 10, text: 'row 10'},
39 | ];
40 |
41 | // simulate fetch()
42 | function skFetch(url){
43 | return new Promise((resolve, reject) => {
44 | setTimeout(() => {
45 | resolve(data);
46 | }, 1000)
47 | });
48 | }
49 |
50 | var test = React.createClass({
51 | getInitialState() {
52 | return {
53 | dataSource : new RefreshableListView.DataSource({rowHasChanged : (row1, row2) => row1 !== row2}),
54 | }
55 | },
56 | componentDidMount() {
57 | this.fetchData(true);
58 | },
59 |
60 | /**
61 | * load data
62 | * @param bool refresh: whether to refresh data, or load more data
63 | * @return Promise
64 | */
65 | fetchData(refresh){
66 | if(refresh){
67 | this.nextPage = 1;
68 | }
69 | // get the data url of next page
70 | var nextDataUrl = dataUrl + '?page=' + this.nextPage;
71 | // I use skFetch() to simulate fetch()
72 | return skFetch(nextDataUrl)
73 | .then((result) => {
74 | var newRows;
75 | if(refresh){ // set rows of dataSource
76 | newRows = result;
77 | }else{// add new rows into dataSource
78 | newRows = this.getRows().concat(result);
79 | }
80 | var newDataSource = this.state.dataSource.cloneWithRows(newRows);
81 | this.setState({
82 | dataSource: newDataSource,
83 | });
84 | this.nextPage++;
85 | }).catch((e)=>{
86 | console.log(e);
87 | });
88 | //.done();
89 | },
90 |
91 | // get all rows of dataSource
92 | getRows() {
93 | var result = this.state.dataSource && this.state.dataSource._dataBlob && this.state.dataSource._dataBlob.s1;
94 | return result ? result : [];
95 | },
96 |
97 | // whether no row in dataSource
98 | isEmpty(){
99 | return this.getRows().length == 0;
100 | },
101 |
102 | renderRow(row) {
103 | return (
104 |
105 | {row.text}
106 |
107 | );
108 | },
109 |
110 | render() {
111 | if(this.isEmpty()){
112 | return (
113 |
114 | {'Please pull down to fresh data, \n pull up to load more data'}
115 |
116 | )
117 | }
118 | return (
119 | this.fetchData(true)} // callback to refresh data (load first page of data), which should return a Promise, I use this promise to tell when to stop loading (render loading view).
123 | onLoadMore={() => this.fetchData(false)} // callback to load more data (load next page of data), which should return a Promise, I use this promise to tell when to stop loading (render loading view)
124 | showLoadMore={true}
125 | renderRow={this.renderRow}
126 | />
127 | );
128 | }
129 | });
130 |
131 | var styles = {
132 | emptyBox: {
133 | flex: 1,
134 | justifyContent: 'center',
135 | alignItems: 'center'
136 | },
137 | emptyTxt: {
138 | fontSize: 23,
139 | fontWeight: 'bold'
140 | },
141 | container: {
142 | flex: 1,
143 | backgroundColor: '#FFF',
144 | },
145 | row: {
146 | padding: 10,
147 | height: height / 10,
148 | backgroundColor: 'yellow',
149 | borderBottomColor: 'grey',
150 | borderBottomWidth: 2,
151 | },
152 | };
153 |
154 | AppRegistry.registerComponent('test', () => test);
155 |
156 | ```
157 | 
158 |
159 | ##Properties
160 |
161 | Any [View property](http://facebook.github.io/react-native/docs/view.html) and the following:
162 |
163 | | Prop | Description | Default |
164 | |---|---|---|
165 | |**`onRefresh`**|callback to refresh data (load first page of data), which should return a Promise, I use this promise to tell when to stop loading (render loading view). |*None*|
166 | |**`onLoadMore`**|callback to load more data (load next page of data), which should return a Promise, I use this promise to tell when to stop loading (render loading view). |*None*|
167 |
168 | ##Methods
169 |
170 | | Method | Description | Params |
171 | |---|---|---|
172 | |**`scrollToTop`**|scroll to top. |*None*|
173 | |**`scrollToBottom`**|scroll to bottom. |*None*|
174 |
--------------------------------------------------------------------------------
/RefreshableListView.js:
--------------------------------------------------------------------------------
1 | var React = require('react-native')
2 | var {
3 | ListView,
4 | StyleSheet,
5 | View,
6 | Text,
7 | Dimensions,
8 | PropTypes,
9 | RefreshControl
10 | } = React;
11 |
12 | var TopButton = require('./TopButton'),
13 | Loader = require('react-native-sk-loader'), // 加载器
14 | {width} = Dimensions.get('window');
15 |
16 | var RefreshableListView = React.createClass({
17 | statics: {
18 | DataSource: ListView.DataSource,
19 | },
20 |
21 | propTypes: {
22 | onRefresh: PropTypes.func.isRequired, // 刷新数据的回调
23 | onLoadMore: PropTypes.func.isRequired, // 加载更多数据的回调
24 | },
25 |
26 | listHeight: 0, // listview(scrollview)高度
27 | contentHeight: 0, // contentView高度
28 |
29 | getDefaultProps() {
30 | return {
31 | showLoadMore: false, // 是否显示加载更多
32 | hasTopButton: false, // 是否显示回到顶部的按钮
33 | }
34 | },
35 |
36 | getInitialState() {
37 | return {
38 | isRefreshing: false, // 是否正在刷新
39 | isLoadingMore: false, // 是否正在加载更多
40 | }
41 | },
42 |
43 | // 渲染脚部
44 | renderFooter: function() {
45 | // 没有更多/下一页数据
46 | if (!this.props.showLoadMore) {
47 | return null;
48 | }
49 |
50 | // 自定义渲染
51 | if (this.props.renderFooter) {
52 | return this.props.renderFooter(this.state.isLoadingMore)
53 | }
54 |
55 | // 默认渲染
56 | return ()
57 | },
58 |
59 | // 划到底部的事件
60 | handleEndReached(e) {
61 | if (! this.props.showLoadMore) return;
62 | this.startLoadMore();
63 | },
64 |
65 | // 是否正在加载
66 | isLoading(){
67 | return this.state.isRefreshing || this.state.isLoadingMore;
68 | },
69 |
70 | // 加载更多/下一页数据
71 | startLoadMore() {
72 | if (this.props.onLoadMore && ! this.isLoading()) {
73 | this.state.isLoadingMore = true;
74 | this.props.onLoadMore().then(this.finishLoadMore, this.finishLoadMore);
75 | }
76 | },
77 | finishLoadMore() {
78 | this.state.isLoadingMore = false;
79 | },
80 |
81 | // 刷新数据
82 | startRefresh() {
83 | if (this.props.onRefresh && ! this.isLoading()) {
84 | this.setState({isRefreshing: true});
85 | this.props.onRefresh().then(this.finishRefresh, this.finishRefresh);
86 | }
87 | },
88 | finishRefresh() {
89 | this.setState({isRefreshing: false});
90 | },
91 |
92 | getScrollResponder() {
93 | return this.refs.listView.getScrollResponder();
94 | },
95 |
96 | setNativeProps(props) {
97 | this.refs.listView.setNativeProps(props)
98 | },
99 |
100 | // 滚动顶部
101 | scrollToTop: function(){
102 | this.refs.listview.scrollTo({y: 0});
103 | },
104 |
105 | // 滚动底部
106 | scrollToBottom: function(animated = true){
107 | if (this.listHeight && this.contentHeight && this.contentHeight > this.listHeight) {
108 | var scrollDistance = this.contentHeight - this.listHeight;
109 | this.refs.listview.scrollTo({
110 | y: scrollDistance,
111 | animated,
112 | });
113 | }
114 | },
115 |
116 | render() {
117 | return (
118 |
119 | {/* 列表页 */}
120 |
136 | }
137 | onLayout={(event)=>{
138 | var layout = event.nativeEvent.layout;
139 | this.listHeight = layout.height;
140 | }}
141 | onContentSizeChange={(width, height)=>{
142 | this.contentHeight = height;
143 | }}
144 | />
145 | {/* 回滚到顶部的按钮 */}
146 | {this.props.hasTopButton && (
147 |
148 | )}
149 |
150 | )
151 | },
152 | });
153 |
154 | // 脚部
155 | var Footer = React.createClass({
156 | render: function(){
157 | var desc = '正在加载更多...';
158 | return (
159 |
160 |
161 |
162 | {desc}
163 |
164 |
165 | )
166 | }
167 | });
168 |
169 | var styles = StyleSheet.create({
170 | loadMore: {
171 | // position: 'absolute',
172 | width: width,
173 | flexDirection: 'row',
174 | justifyContent: 'center',
175 | alignItems: 'center',
176 | backgroundColor: 'transparent',
177 | height: 60,
178 | },
179 | description: {
180 | color: '#333',
181 | marginLeft: 6,
182 | },
183 | activityIndicator:{
184 | alignSelf: 'center',
185 | },
186 | })
187 |
188 | module.exports = RefreshableListView;
189 |
--------------------------------------------------------------------------------
/TopButton.js:
--------------------------------------------------------------------------------
1 | var React = require('react-native')
2 | var {
3 | StyleSheet,
4 | Image,
5 | TouchableOpacity,
6 | PropTypes
7 | } = React;
8 |
9 | var TopButton = React.createClass({
10 | propTypes: {
11 | onPress: PropTypes.func,
12 | },
13 | render: function(){
14 | return (
15 |
16 |
17 |
18 | )
19 | }
20 | });
21 |
22 | var styles = StyleSheet.create({
23 | topButton:{
24 | position:'absolute',
25 | right:5,
26 | bottom:45,
27 | width:55,
28 | height:55,
29 | backgroundColor:'rgba(0,0,0,0)',
30 | },
31 | topButtomImg: {
32 | width:40,
33 | height:40,
34 | }
35 | })
36 |
37 | module.exports = TopButton;
38 |
--------------------------------------------------------------------------------
/demo.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shigebeyond/react-native-sk-refreshable-listview/bcc825b6c09ba0b32fe620c348393dd0793f4937/demo.gif
--------------------------------------------------------------------------------
/img/va-top.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shigebeyond/react-native-sk-refreshable-listview/bcc825b6c09ba0b32fe620c348393dd0793f4937/img/va-top.png
--------------------------------------------------------------------------------
/img/va-top@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shigebeyond/react-native-sk-refreshable-listview/bcc825b6c09ba0b32fe620c348393dd0793f4937/img/va-top@2x.png
--------------------------------------------------------------------------------
/img/va-top@3x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/shigebeyond/react-native-sk-refreshable-listview/bcc825b6c09ba0b32fe620c348393dd0793f4937/img/va-top@3x.png
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react-native-sk-refreshable-listview",
3 | "version": "1.0.0",
4 | "description": "Component wraps ListView, supports: 1 pull down to refresh 2 pull up to load more 3 scroll to top 4 scroll to bottom",
5 | "main": "RefreshableListView.js",
6 | "scripts": {
7 | "test": "echo \"Error: no test specified\" && exit 1"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/shigebeyond/react-native-sk-refreshable-listview.git"
12 | },
13 | "keywords": [
14 | "react-native",
15 | "refreshable-list-view",
16 | "pull-down-refresh",
17 | "pull-up-loadmore"
18 | ],
19 | "dependencies": {
20 | "react-native-sk-loader": "^1.0.0"
21 | },
22 | "author": "shigebeyond",
23 | "license": "MIT",
24 | "bugs": {
25 | "url": "https://github.com/shigebeyond/react-native-sk-refreshable-listview/issues"
26 | },
27 | "homepage": "https://github.com/shigebeyond/react-native-sk-refreshable-listview#readme"
28 | }
29 |
--------------------------------------------------------------------------------