该准备的东西都已经准备好了。在这篇文章里,我们就开始实现下拉刷新功能吧。
一、大体的逻辑分析
我们来简单分析一下需要做的逻辑吧。首先分析头布局有几种状态。不下拉时,为正常状态,此时头布局隐藏。下拉到一定高度,提示信息变为“下拉刷新”,箭头朝下,此为下拉状态。再往下拉,提示信息变为“松开刷新”,箭头朝上,此为提示刷新状态。而此时松开手指,则执行刷新操作,头布局变为进度条显示,箭头消失,此为正在刷新状态。相反的,其他状态下松开手指,都不执行刷新操作,应该将头布局恢复到正常状态。因为可确定头布局的状态有四种。
我们根据这四种状态,确定我们要做的事情。要监听ListView的滚动,故要实现OnScrollListener接口。还要监听手指触摸事件,根据手指的下拉移动来改变头布局的显示效果,根据手指的抬起来判断是否进行刷新操作,因为要实现onTouchEvent方法。也就是说,头布局状态的改变应该随着手指的移动而改变,因此在onTouchEvent里面我们要实现上面分析的四种状态的改变。当然,状态改变就意味着头布局显示效果的改变,这里可以嵌套在onTouchEvent方法里面。但考虑到避免方法臃肿,以及其他地方可能也需要改变头布局界面,比如数据加载完成后等情况,因此专门将头布局界面的改变抽取出来,凝聚为一个方法。
然后就是数据刷新,刷新操作要在MyListView里执行,但是数据要在MainActivity中获取。老规矩,用接口回调即可。
好了,基本上大体的逻辑就这么多了。下面我们将上面的分析转化为代码。
二、代码编写
废话我就不多说了,上面的分析很清楚了。继续完善MyListView即可。代码如下:
1 package com.fuly.load;
2
3 import android.content.Context;
4 import android.util.AttributeSet;
5 import android.view.LayoutInflater;
6 import android.view.MotionEvent;
7 import android.view.View;
8 import android.view.ViewGroup;
9 import android.widget.AbsListView;
10 import android.widget.AbsListView.OnScrollListener;
11 import android.widget.ImageView;
12 import android.widget.ListView;
13 import android.widget.ProgressBar;
14 import android.widget.TextView;
15
16 public class MyListView extends ListView implements OnScrollListener{
17
18 private View header;//头布局
19
20 private int headerHeight;//头布局自身的高度
21
22 private int scrollState;//当前滚动状态
23 private int firstVisibleItem;//当前可见的第一个item
24 private int startY;//刚开始触摸屏幕时的Y值
25
26 private int curState = 0;//当前header状态,默认为0
27 private final int NORMAL = 0;//正常状态
28 private final int PULL = 1;//状态下拉
29 private final int RELEASE = 2;//提示刷新状态
30 private final int RELEASING = 3;//状态正在刷新
31
32 private boolean canPull = false;//是否可以执行下拉操作
33
34 private refresfListener mListener;//回调接口
35
36
37 //三个构造方法都要重写
38 public MyListView(Context context) {
39 super(context);
40 initView( context);
41
42 }
43 public MyListView(Context context, AttributeSet attrs) {
44 super(context, attrs);
45 initView( context);
46
47 }
48 public MyListView(Context context, AttributeSet attrs, int defStyle) {
49 super(context, attrs, defStyle);
50 initView( context);
51
52 }
53
54 //定义回调接口
55 public interface refresfListener{
56 void refresh();
57 }
58 public void setOnRefreshListener(refresfListener listener){
59 this.mListener = listener;
60 }
61
62
63 public void initView(Context context){
64
65 header = LayoutInflater.from(context).inflate(R.layout.header, null);
66 notifyView(header);
67 headerHeight = header.getMeasuredHeight();//获取header的高度
68
69 // headerHeight = header.getHeight();
70 paddingTop(-headerHeight);
71 //将头布局加进去
72 this.addHeaderView(header);
73
74 this.setOnScrollListener(this);
75 }
76
77
78
79
80 /**
81 * 该方法为通知父布局,子布局view的宽度和高度
82 * @param view:子布局
83 */
84 private void notifyView(View view){
85
86 ViewGroup.LayoutParams p = view.getLayoutParams();
87
88 if(p == null){
89 p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
90
91 }
92
93 //spec表示当前子view左右边距,padding表示子view的左右内边距
94 //childDimension:子view的宽度
95 int width = ViewGroup.getChildMeasureSpec(0, 0, p.width);
96
97 int height;
98 int tempHeight = p.height;
99 if(tempHeight>0){
100 //子布局高度不为空,需要填充这个布局
101 height = MeasureSpec.makeMeasureSpec(tempHeight, MeasureSpec.EXACTLY);
102 }else{
103 //高度为0,则不需要填充
104 height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
105 }
106
107 //然后告诉父布局,子布局的高度和宽度
108 view.measure(width, height);
109 }
110
111
112 //该方法设定header的paddingTop
113 private void paddingTop(int pt){
114 header.setPadding(header.getPaddingLeft(), pt, header.getPaddingRight(), header.getPaddingBottom());
115 header.invalidate();
116 }
117
118
119
120
121 /***
122 * 监听当前滚动状态
123 * scrollState:当前滚动状态
124 */
125 public void onScrollStateChanged(AbsListView view, int scrollState) {
126 //记录当前的滚动状态
127 this.scrollState = scrollState;
128
129 }
130
131 /***
132 * 监听当前滚动的item
133 * firstVisibleItem:当前可见的第一个item
134 * visibleItemCount:当前共有多少个item可见
135 * totalItemCount:总共有多少个item
136 *
137 */
138 public void onScroll(AbsListView view, int firstVisibleItem,
139 int visibleItemCount, int totalItemCount) {
140
141 this.firstVisibleItem = firstVisibleItem;
142
143
144 }
145
146
147 //触屏事件
148 public boolean onTouchEvent(MotionEvent ev) {
149
150 switch(ev.getAction()){
151 //手指落到屏幕上时
152 case MotionEvent.ACTION_DOWN:
153 //如果当前可见的第一个item为第0号,说明ListView位于顶端,可以执行下拉刷新
154 if(firstVisibleItem == 0){
155 canPull = true;
156 startY = (int) ev.getY();
157 }
158
159 break;
160 //手指在屏幕上拖动时
161 case MotionEvent.ACTION_MOVE:
162 if(canPull){
163 touchMove(ev);
164 }
165 break;
166 //手指离开屏幕时
167 case MotionEvent.ACTION_UP:
168 canPull = false;
169 if(curState == RELEASE){
170 curState = RELEASING;
171 refreshHeaderByState();
172 //这里添加刷新数据的逻辑
173 mListener.refresh();
174 }else{
175 curState = NORMAL;
176 refreshHeaderByState();
177 paddingTop(-headerHeight);
178 }
179
180 break;
181 }
182
183 return super.onTouchEvent(ev);
184 }
185
186 /**
187 * 该方法根据触摸屏幕滑动来改变STATE,即改变当前状态
188 * @param ev
189 */
190 private void touchMove(MotionEvent ev) {
191
192 int tempY = (int) ev.getY();
193 int space = tempY -startY;//移动的距离
194 int topdding = space-headerHeight;
195 paddingTop(topdding);//即时设定头布局的隐藏高度
196 if(space>headerHeight&&space<headerHeight+50&&scrollState == SCROLL_STATE_TOUCH_SCROLL){
197 curState = PULL;//设定为下拉状态
198 refreshHeaderByState();
199 }
200 if(space>headerHeight+50){
201 curState = RELEASE;//设定为提示刷新状态
202 refreshHeaderByState();
203 }
204
205 if(space<headerHeight){
206 curState = NORMAL;//设定为正常状态
207 refreshHeaderByState();
208 }
209
210
211 }
212
213
214 /**
215 * 根据当前状态更改header的显示界面
216 *
217 */
218 private void refreshHeaderByState( ){
219 ProgressBar pb = (ProgressBar) header.findViewById(R.id.progress_bar);
220 ImageView img = (ImageView) header.findViewById(R.id.img_arrow);
221 TextView tv = (TextView) header.findViewById(R.id.textinfo);
222
223 switch(curState){
224
225 case NORMAL:
226 pb.setVisibility(View.GONE);
227 img.setVisibility(View.VISIBLE);
228 img.setImageResource(R.drawable.down_arrow);
229 tv.setText("下拉刷新");
230 break;
231 case PULL:
232 pb.setVisibility(View.GONE);
233 img.setVisibility(View.VISIBLE);
234 img.setImageResource(R.drawable.down_arrow);
235 tv.setText("下拉刷新");
236 break;
237 case RELEASE:
238 pb.setVisibility(View.GONE);
239 img.setVisibility(View.VISIBLE);
240 img.setImageResource(R.drawable.up_arrow);
241 tv.setText("松开刷新");
242 break;
243 case RELEASING:
244 pb.setVisibility(View.VISIBLE);
245 img.setVisibility(View.GONE);
246 tv.setText("正在刷新");
247 break;
248
249
250 }
251
252 }
253
254 //数据刷新完成后的操作
255 public void refreshFinish(){
256
257 curState = NORMAL;
258 paddingTop(-headerHeight);
259 refreshHeaderByState();
260 }
261
262
263
264 }
接下来就是MainActivity中的代码了。如下:
1 package com.fuly.load;
2
3 import java.util.ArrayList;
4 import java.util.List;
5
6 import com.fuly.load.MyListView.refresfListener;
7
8 import android.os.Bundle;
9 import android.os.Handler;
10 import android.app.Activity;
11
12 public class MainActivity extends Activity implements refresfListener{
13
14 private MyListView lv;
15 private List<MyData> mDatas = new ArrayList<MyData>();
16 private MyAdapter mAdapter;
17
18
19 protected void onCreate(Bundle savedInstanceState) {
20 super.onCreate(savedInstanceState);
21 setContentView(R.layout.activity_main);
22
23 initData();//该方法初始化数据
24 lv = (MyListView) findViewById(R.id.list_view);
25 lv.setOnRefreshListener(this);//设定回调接口
26 mAdapter = new MyAdapter(this, mDatas);
27 lv.setAdapter(mAdapter);
28
29
30 }
31
32
33 /**
34 * 该方法初始化数据,即提供初始的素材
35 */
36 private void initData() {
37 for(int i = 0;i<12;i++){
38 MyData md = new MyData("你好,我是提前设定的");
39 mDatas.add(md);
40 }
41
42 }
43 /**
44 * 提供刷新数据
45 */
46 private void getRefreshData() {
47 for(int i = 0;i<3;i++){
48 MyData md = new MyData("你好,我是刷新进来的");
49 mDatas.add(i, md);
50 }
51
52 }
53
54
55 //重写回调方法
56 public void refresh() {
57
58 //在这里之所以使用Handler,是想让操作延迟,这样子效果看起来更
59 //清晰,实际项目中,是 不需要的
60 Handler mHandler = new Handler();
61 mHandler.postDelayed(new Runnable(){
62
63 @Override
64 public void run() {
65 //获得刷新数据
66 getRefreshData();
67 //刷新ListView
68 mAdapter.notifyDataSetChanged();
69 //lv.setSelection(mDatas.size()-1);
70
71 //刷新后
72 lv.refreshFinish();
73
74 }
75
76 }, 5000 );
77
78
79
80 }
81 }
好了,快快运行下程序,体验下拉刷新的效果吧。至此,ListView实现下拉刷新,我们讲解完毕了。