Benjy's Blog

下拉刷新封装

2014-10-21

忙了两周,楼里 app 终于完成了,25号正式上线,顺便吐槽一下接私活果然好坑,特别是这种工期比较着急的,不过今天款项下来了,拿到 money 之后感觉整个人萌萌哒~~ 废话不多说,今天分享一下自己在工作中对 Android-PullToRefresh的封装

简介

目前 Android 上实现下拉刷新的方式主要有两种:

  • 通过修改 HeaderView 的 Padding 实现,常见作法是使用自定义组件继承 ListView,一个类就可以搞定
  • 在 istview 底部套一层 ScrollView 或者 LinearLayout,通过移动 ListView 来实现

第一种方法简单但是碰到复杂的 ListView,特别是带有图片的的item就会特别卡,因为主线程刷新界面也需要时间,而改变之后的效果只有刷新之后才能看到,所以给人的感觉是一顿一顿的。第二种方法就不会出现这样的效果,这里推荐的是 Android-PullToRefresh,具体使用大家可以看该项目的 demo,该项目对常见的带滑动的组件都有支持,很强大,而且可以定制。

使用

我的封装主要是对消息处理的封装和页码计算的封装,使你只用写自己逻辑而不用管这些细节
直接看代码, 首先是封装类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
package com.louli.activity.louli;

import java.text.SimpleDateFormat;
import java.util.List;
import android.app.Activity;
import android.os.Message;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.Toast;

import com.handmark.pulltorefresh.library.PullToRefreshBase;
import com.handmark.pulltorefresh.library.PullToRefreshBase.Mode;
import com.handmark.pulltorefresh.library.PullToRefreshBase.OnRefreshListener2;
import com.handmark.pulltorefresh.library.PullToRefreshListView;
import com.louli.community.R;

/**
* 带下拉刷新的activity 需要继承该类
*
* @author KokerWang
*
*/
public abstract class ListViewBaseActivity extends Activity {

/**
* 分页的页码,当前第几页
*/
public int pageCount = 1;

/**
* 当前刷新状态  -1 下拉 , 1  上拉 , 0 默认值
*/
public int status;

/**
* 获取页码 如果界面里有1个以上的 PullToRefreshListView组件需要重重写 pageCount的get set 方法,自己记录页码
*
* @return
*/
public int getPageCount() {
return pageCount;
}

/**
* 设置页码 如果界面里有1个以上的 PullToRefreshListView组件需要重重写 pageCount的get set 方法,自己记录页码
*
* @param pageCount
*/
public void setPageCount(int pageCount) {
this.pageCount = pageCount;
}

/**
* 返回数据内容的集合
*
* @return
*/
public abstract List<?> getList();

/**
* 返回ListView
*
* @return
*/
public abstract PullToRefreshListView getListView();

/**
* 返回适配器
*
* @return
*/
public abstract BaseAdapter getAdapter();

/**
* 该方法主要完成异步获取数据分别需要处理三种结果 获取成功 msg.what=LOAD_SUCCESS 获取失败
* msg.what=LOAD_FAIL 没有数据 msg.what=LOAD_NODATA 获取成功的数据放在msg.obj 里 ,List 类型
*/
public abstract void loadData();

/**
* 初始化带刷新的ListView
*
* @param id
* 组件id
* @return
*/
protected PullToRefreshListView initListView(int id) {
PullToRefreshListView listView = (PullToRefreshListView) findViewById(id);
if (listView == null) {
return null;
}
return initListView(listView);
}

/**
* 初始化带刷新的ListView
*
* @param listView
* 帅新组件
* @return
*/
protected PullToRefreshListView initListView(PullToRefreshListView listView) {
if (listView == null) {
return null;
}
listView.setMode(Mode.BOTH);
//以下为自定义提示语 可以不写使用默认的
listView.getLoadingLayoutProxy(falsetrue).setPullLabel(getResources().getString(R.string.push_label));
listView.getLoadingLayoutProxy(falsetrue).setRefreshingLabel(getResources().getString(R.string.refreshing_label));
listView.getLoadingLayoutProxy(falsetrue).setReleaseLabel(getResources().getString(R.string.release_label));
listView.getLoadingLayoutProxy(truefalse).setPullLabel(getResources().getString(R.string.pull_label));
listView.getLoadingLayoutProxy(truefalse).setRefreshingLabel(getResources().getString(R.string.refreshing_label));
listView.getLoadingLayoutProxy(truefalse).setReleaseLabel(getResources().getString(R.string.release_label));

listView.setOnRefreshListener(new OnRefreshListener2Impl());
return listView;
}

/**
* 获取成功的处理, 主要在handler 里面调用
*
* @param msg
*/
@SuppressWarnings({ "unchecked""rawtypes" })
protected void loadSucess(Message msg) {
List ls = (List) msg.obj;
if (getPageCount() == 1) {
getList().clear();
getListView().setMode(Mode.BOTH);
}
if (ls != null) {
getList().addAll(ls);
}
getAdapter().notifyDataSetChanged();
getListView().onRefreshComplete();
};

/**
* 获取成功但是没有数据的处理, 主要在handler 里面调用
*
* @param msg
*/
protected void loadZeroDate(Message msg) {
if (getPageCount() == 1) {
Toast.makeText(this, R.string.zero_date, Toast.LENGTH_SHORT).show();

} else {
Toast.makeText(this, R.string.msg_last, Toast.LENGTH_SHORT).show();
}
getAdapter().notifyDataSetChanged();
getListView().onRefreshComplete();
getListView().setMode(Mode.PULL_FROM_START);
};

/**
* 获取失败的处理, 主要在handler 里面调用
*
* @param msg
*/
protected void loadFalt(Message msg) {
Toast.makeText(this, R.string.load_error, Toast.LENGTH_SHORT).show();
getAdapter().notifyDataSetChanged();
getListView().onRefreshComplete();
};

/**
* 上拉刷新及加载更多的回调接口实现类
*
* @author tom
*
*/
private class OnRefreshListener2Impl implements OnRefreshListener2<ListView> {
SimpleDateFormat sdf = new SimpleDateFormat(getResources().getString(R.string.refresh_for) + ":MM-dd hh:mm");

@Override
public void onPullDownToRefresh(PullToRefreshBase<ListView> refreshView) {
//下拉
status = 1;
setPageCount(1);
loadData();
String label = sdf.format(System.currentTimeMillis());
refreshView.getLoadingLayoutProxy().setLastUpdatedLabel(label);
}

@Override
public void onPullUpToRefresh(PullToRefreshBase<ListView> refreshView) {
//上拉
status = -1;
setPageCount(getPageCount() + 1);
loadData();
String label = sdf.format(System.currentTimeMillis());
refreshView.getLoadingLayoutProxy().setLastUpdatedLabel(label);
}
}

}

如果你的界面里有一个以上的 PullToRefreshListView 组件,那你需要额外重写 getPageCount 和 setPageCount 来自己控制页码,当然这个时候其他几个重写的方法也需要你加上判断,具体示例我会在文章最后附上

下面看例子代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
package com.test;

import android.os.Message;


public class ChannelExhibitionListActivity extends ListViewBaseActivity {

// 界面组件
PullToRefreshListView _listView;
List<ChannelExhibitionListItem> _list;
ChannelExhibitionListAdapter _adapter;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.channel_exhibition_list_activity);
init();
}

private void init() {
// 初始化组件及适配器
_listView = initListView(R.id.listView);
// 获取内部listview 推荐这样用而不是直接给_listview设置参数
ListView actualListView = _listView.getRefreshableView();
_adapter = new ChannelExhibitionListAdapter(this);
_list = new ArrayList<ChannelExhibitionListItem>();
_adapter.setList(_list);
actualListView.setOnItemClickListener(new OnItemClickListener() {

@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
// 不要忘了这一行代码  因为自带了headview 所以postiton加了1 这里要减去
position--;
}
});
actualListView.setAdapter(_adapter);

}

// 异步任务接受处理
Handler _handler = new Handler() {
public void handleMessage(android.os.Message msg) {
switch (msg.what) {
case LOAD_SUCCESS:
loadSucess(msg);
break;
case LOAD_FAIL:
loadFalt(msg);
break;
case LOAD_NODATA:
loadZeroDate(msg);
break;
}
}
};

// 获取数据的线程
Thread loadThread;

/**
* 更改数据并刷新adapter
*/
@Override
public void loadData() {
createCountryThread();
loadThread.start();
}

/**
* 初始化线程
*/
private void createCountryThread() {
loadThread = new Thread(new Runnable() {

@Override
public void run() {
List<ChannelExhibitionListItem> channels = new ArrayList<ChannelExhibitionListItem>();
try {
// 把页码拼接到url
StringBuffer loadService = new StringBuffer();
loadService.append("?r=findlist&pagesize=15&pagenow=" + getPageCount());

String res = HttpConstants.getPHPRequest("url", loadService.toString());
if (res == null || res.equals("")) {
throw new Exception("resoult=" + res);
}
JSONObject resultJsonObject = new JSONObject(res);
int code = resultJsonObject.getInt("returncode");
if (code == 0) {
// 业务处理  大概就是解析出list
JSONArray datas = resultJsonObject.getJSONArray("result");
if (datas.length() == 0) {
Message m = _handler.obtainMessage(LOAD_NODATA);
_handler.sendMessage(m);
return;
}
int size = datas.length();
for (int i = 0; i < size; i++) {
JSONObject obj = (JSONObject) datas.get(i);
ChannelExhibitionListItem item = new ChannelExhibitionListItem();
item.setId("");

channels.add(item);
}
Message message = _handler.obtainMessage(LOAD_SUCCESS);
message.obj = channels;
_handler.sendMessage(message);
return;
}else if(code == 1){
Message m = _handler.obtainMessage(LOAD_NODATA);
_handler.sendMessage(m);
return;
}
// 获取失败
Message m = _handler.obtainMessage(LOAD_FAIL);
_handler.sendMessage(m);
} catch (Exception e) {
Message m = _handler.obtainMessage(LOAD_FAIL);
_handler.sendMessage(m);
} finally {
//关闭进度条
//Util.dismissWaitingDialog(pd);
}
}
});
}

@Override
public List<?> getList() {
return _list;
}

@Override
public PullToRefreshListView getListView() {
return _listView;
}

@Override
public BaseAdapter getAdapter() {
return _adapter;
}
}

以上代码只是逻辑代码,复制在自己的项目中是不能运行的,参考着就可以写了,因为我们的项目是使用的 httpClient+Handler 回调,所以用这个特别方便, 如果你们项目使用 Volley 那就更简单了,稍微修改一下就能用。

最后是布局 布局只需要把 ListView 替换实现就可以 其他的一下属性和 ListView 通用。

1
2
3
4
5
6
7
8
9
10
11
12
13
<com.handmark.pulltorefresh.library.PullToRefreshListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:cacheColorHint="#00000000"
android:divider="#19000000"
android:dividerHeight="1dp"
android:fadingEdge="none"
android:fastScrollEnabled="false"
android:footerDividersEnabled="true"
android:headerDividersEnabled="true"
android:smoothScrollbar="true" >
</com.handmark.pulltorefresh.library.PullToRefreshListView>

多个 PullToRefreshListView 组件时的示例代码 , 其他几个重写的方法也要这样修改

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/**
*当前第几个PullToRefreshListView的标示
*/
int num=1;
/**
* 页码
*/
private int count_p = 1;
private int count_r = 1;


/**
* 获取页码
*
* @return
*/
public int getPageCount() {
if (num ==1) {
return count_p;
}else{
return count_r;
}
}

/**
* 设置页码
*
* @param pageCount
*/
public void setPageCount(int pageCount) {
if (num ==1) {
this.count_p = pageCount;
}else{
this.count_r = pageCount;
}
}

写完啦,我要去腐败喽。

Tags: Android