最近项目中刚好有下拉刷新的需求,于是找了一些开源的资料研究了一下,看到网上有很多实现方式,有的继承Linearlayout,有的重写ScrollView,还有的重写Gridview,看到最多的还是重写listview,这里采用哪一种方式的实现没有对错之分,关键要看需求的设计。
这里还是采用大家用的最多的listview实现下拉刷新的操作。
效果如下图:
下面我们来看下代码:
其主要代码是listview实现:
001 | public class PullToRefreshListView extends ListView implements OnScrollListener { |
002 |
003 | private final static String TAG = "PullToRefreshListView" ; |
004 |
005 | // 下拉刷新标志 |
006 | private final static int PULL_To_REFRESH = 0 ; |
007 | // 松开刷新标志 |
008 | private final static int RELEASE_To_REFRESH = 1 ; |
009 | // 正在刷新标志 |
010 | private final static int REFRESHING = 2 ; |
011 | // 刷新完成标志 |
012 | private final static int DONE = 3 ; |
013 |
014 | private LayoutInflater inflater; |
015 |
016 | private LinearLayout headView; |
017 | private TextView tipsTextview; |
018 | private TextView lastUpdatedTextView; |
019 | private ImageView arrowImageView; |
020 | private ProgressBar progressBar; |
021 | // 用来设置箭头图标动画效果 |
022 | private RotateAnimation animation; |
023 | private RotateAnimation reverseAnimation; |
024 |
025 | // 用于保证startY的值在一个完整的touch事件中只被记录一次 |
026 | private boolean isRecored; |
027 |
028 | private int headContentWidth; |
029 | private int headContentHeight; |
030 | private int headContentOriginalTopPadding; |
031 |
032 | private int startY; |
033 | private int firstItemIndex; |
034 | private int currentScrollState; |
035 |
036 | private int state; |
037 |
038 | private boolean isBack; |
039 |
040 | public OnRefreshListener refreshListener; |
041 |
042 | public PullToRefreshListView(Context context, AttributeSet attrs) { |
043 | super (context, attrs); |
044 | init(context); |
045 | } |
046 |
047 | public PullToRefreshListView(Context context, AttributeSet attrs, int defStyle) { |
048 | super (context, attrs, defStyle); |
049 | init(context); |
050 | } |
051 |
052 | private void init(Context context) { |
053 | //设置滑动效果 |
054 | animation = new RotateAnimation( 0 , - 180 , |
055 | RotateAnimation.RELATIVE_TO_SELF, 0 .5f, |
056 | RotateAnimation.RELATIVE_TO_SELF, 0 .5f); |
057 | animation.setInterpolator( new LinearInterpolator()); |
058 | animation.setDuration( 100 ); |
059 | animation.setFillAfter( true ); |
060 |
061 | reverseAnimation = new RotateAnimation(- 180 , 0 , |
062 | RotateAnimation.RELATIVE_TO_SELF, 0 .5f, |
063 | RotateAnimation.RELATIVE_TO_SELF, 0 .5f); |
064 | reverseAnimation.setInterpolator( new LinearInterpolator()); |
065 | reverseAnimation.setDuration( 100 ); |
066 | reverseAnimation.setFillAfter( true ); |
067 |
068 | inflater = LayoutInflater.from(context); |
069 | headView = (LinearLayout) inflater.inflate(R.layout.pull_to_refresh_head, null ); |
070 |
071 | arrowImageView = (ImageView) headView.findViewById(R.id.head_arrowImageView); |
072 | arrowImageView.setMinimumWidth( 50 ); |
073 | arrowImageView.setMinimumHeight( 50 ); |
074 | progressBar = (ProgressBar) headView.findViewById(R.id.head_progressBar); |
075 | tipsTextview = (TextView) headView.findViewById(R.id.head_tipsTextView); |
076 | lastUpdatedTextView = (TextView) headView.findViewById(R.id.head_lastUpdatedTextView); |
077 |
078 | headContentOriginalTopPadding = headView.getPaddingTop(); |
079 |
080 | measureView(headView); |
081 | headContentHeight = headView.getMeasuredHeight(); |
082 | headContentWidth = headView.getMeasuredWidth(); |
083 |
084 | headView.setPadding(headView.getPaddingLeft(), - 1 * headContentHeight, headView.getPaddingRight(), headView.getPaddingBottom()); |
085 | headView.invalidate(); |
086 |
087 | //System.out.println("初始高度:"+headContentHeight); |
088 | //System.out.println("初始TopPad:"+headContentOriginalTopPadding); |
089 |
090 | addHeaderView(headView); |
091 | setOnScrollListener( this ); |
092 | } |
093 |
094 | public void onScroll(AbsListView view, int firstVisiableItem, int visibleItemCount, int totalItemCount) { |
095 | firstItemIndex = firstVisiableItem; |
096 | } |
097 |
098 | public void onScrollStateChanged(AbsListView view, int scrollState) { |
099 | currentScrollState = scrollState; |
100 | } |
101 |
102 | public boolean onTouchEvent(MotionEvent event) { |
103 | switch (event.getAction()) { |
104 | case MotionEvent.ACTION_DOWN: |
105 | if (firstItemIndex == 0 && !isRecored) { |
106 | startY = ( int ) event.getY(); |
107 | isRecored = true ; |
108 | //System.out.println("当前-按下高度-ACTION_DOWN-Y:"+startY); |
109 | } |
110 | break ; |
111 |
112 | case MotionEvent.ACTION_CANCEL: //失去焦点&取消动作 |
113 | case MotionEvent.ACTION_UP: |
114 |
115 | if (state != REFRESHING) { |
116 | if (state == DONE) { |
117 | //System.out.println("当前-抬起-ACTION_UP:DONE什么都不做"); |
118 | } |
119 | else if (state == PULL_To_REFRESH) { |
120 | state = DONE; |
121 | changeHeaderViewByState(); |
122 | //System.out.println("当前-抬起-ACTION_UP:PULL_To_REFRESH-->DONE-由下拉刷新状态到刷新完成状态"); |
123 | } |
124 | else if (state == RELEASE_To_REFRESH) { |
125 | state = REFRESHING; |
126 | changeHeaderViewByState(); |
127 | onRefresh(); |
128 | //System.out.println("当前-抬起-ACTION_UP:RELEASE_To_REFRESH-->REFRESHING-由松开刷新状态,到刷新完成状态"); |
129 | } |
130 | } |
131 |
132 | isRecored = false ; |
133 | isBack = false ; |
134 |
135 | break ; |
136 |
137 | case MotionEvent.ACTION_MOVE: |
138 | int tempY = ( int ) event.getY(); |
139 | //System.out.println("当前-滑动-ACTION_MOVE Y:"+tempY); |
140 | if (!isRecored && firstItemIndex == 0 ) { |
141 | //System.out.println("当前-滑动-记录拖拽时的位置 Y:"+tempY); |
142 | isRecored = true ; |
143 | startY = tempY; |
144 | } |
145 | if (state != REFRESHING && isRecored) { |
146 | // 可以松开刷新了 |
147 | if (state == RELEASE_To_REFRESH) { |
148 | // 往上推,推到屏幕足够掩盖head的程度,但还没有全部掩盖 |
149 | if ((tempY - startY < headContentHeight+ 20 ) |
150 | && (tempY - startY) > 0 ) { |
151 | state = PULL_To_REFRESH; |
152 | changeHeaderViewByState(); |
153 | //System.out.println("当前-滑动-ACTION_MOVE:RELEASE_To_REFRESH--》PULL_To_REFRESH-由松开刷新状态转变到下拉刷新状态"); |
154 | } |
155 | // 一下子推到顶 |
156 | else if (tempY - startY <= 0 ) { |
157 | state = DONE; |
158 | changeHeaderViewByState(); |
159 | //System.out.println("当前-滑动-ACTION_MOVE:RELEASE_To_REFRESH--》DONE-由松开刷新状态转变到done状态"); |
160 | } |
161 | // 往下拉,或者还没有上推到屏幕顶部掩盖head |
162 | else { |
163 | // 不用进行特别的操作,只用更新paddingTop的值就行了 |
164 | } |
165 | } |
166 | // 还没有到达显示松开刷新的时候,DONE或者是PULL_To_REFRESH状态 |
167 | else if (state == PULL_To_REFRESH) { |
168 | // 下拉到可以进入RELEASE_TO_REFRESH的状态 |
169 | if (tempY - startY >= headContentHeight+ 20 && currentScrollState == SCROLL_STATE_TOUCH_SCROLL) { |
170 | state = RELEASE_To_REFRESH; |
171 | isBack = true ; |
172 | changeHeaderViewByState(); |
173 | //System.out.println("当前-滑动-PULL_To_REFRESH--》RELEASE_To_REFRESH-由done或者下拉刷新状态转变到松开刷新"); |
174 | } |
175 | // 上推到顶了 |
176 | else if (tempY - startY <= 0 ) { |
177 | state = DONE; |
178 | changeHeaderViewByState(); |
179 | //System.out.println("当前-滑动-PULL_To_REFRESH--》DONE-由Done或者下拉刷新状态转变到done状态"); |
180 | } |
181 | } |
182 | // done状态下 |
183 | else if (state == DONE) { |
184 | if (tempY - startY > 0 ) { |
185 | state = PULL_To_REFRESH; |
186 | changeHeaderViewByState(); |
187 | //System.out.println("当前-滑动-DONE--》PULL_To_REFRESH-由done状态转变到下拉刷新状态"); |
188 | } |
189 | } |
190 |
191 | // 更新headView的size |
192 | if (state == PULL_To_REFRESH) { |
193 | int topPadding = ( int )((- 1 * headContentHeight + (tempY - startY))); |
194 | headView.setPadding(headView.getPaddingLeft(), topPadding, headView.getPaddingRight(), headView.getPaddingBottom()); |
195 | headView.invalidate(); |
196 | //System.out.println("当前-下拉刷新PULL_To_REFRESH-TopPad:"+topPadding); |
197 | } |
198 |
199 | // 更新headView的paddingTop |
200 | if (state == RELEASE_To_REFRESH) { |
201 | int topPadding = ( int )((tempY - startY - headContentHeight)); |
202 | headView.setPadding(headView.getPaddingLeft(), topPadding, headView.getPaddingRight(), headView.getPaddingBottom()); |
203 | headView.invalidate(); |
204 | //System.out.println("当前-释放刷新RELEASE_To_REFRESH-TopPad:"+topPadding); |
205 | } |
206 | } |
207 | break ; |
208 | } |
209 | return super .onTouchEvent(event); |
210 | } |
211 |
212 | // 当状态改变时候,调用该方法,以更新界面 |
213 | private void changeHeaderViewByState() { |
214 | switch (state) { |
215 | case RELEASE_To_REFRESH: |
216 |
217 | arrowImageView.setVisibility(View.VISIBLE); |
218 | progressBar.setVisibility(View.GONE); |
219 | tipsTextview.setVisibility(View.VISIBLE); |
220 | lastUpdatedTextView.setVisibility(View.VISIBLE); |
221 |
222 | arrowImageView.clearAnimation(); |
223 | arrowImageView.startAnimation(animation); |
224 |
225 | tipsTextview.setText(R.string.pull_to_refresh_release_label); |
226 |
227 | //Log.v(TAG, "当前状态,松开刷新"); |
228 | break ; |
229 | case PULL_To_REFRESH: |
230 |
231 | progressBar.setVisibility(View.GONE); |
232 | tipsTextview.setVisibility(View.VISIBLE); |
233 | lastUpdatedTextView.setVisibility(View.VISIBLE); |
234 | arrowImageView.clearAnimation(); |
235 | arrowImageView.setVisibility(View.VISIBLE); |
236 | if (isBack) { |
237 | isBack = false ; |
238 | arrowImageView.clearAnimation(); |
239 | arrowImageView.startAnimation(reverseAnimation); |
240 | } |
241 | tipsTextview.setText(R.string.pull_to_refresh_pull_label); |
242 |
243 | //Log.v(TAG, "当前状态,下拉刷新"); |
244 | break ; |
245 |
246 | case REFRESHING: |
247 | //System.out.println("刷新REFRESHING-TopPad:"+headContentOriginalTopPadding); |
248 | headView.setPadding(headView.getPaddingLeft(), headContentOriginalTopPadding, headView.getPaddingRight(), headView.getPaddingBottom()); |
249 | headView.invalidate(); |
250 |
251 | progressBar.setVisibility(View.VISIBLE); |
252 | arrowImageView.clearAnimation(); |
253 | arrowImageView.setVisibility(View.GONE); |
254 | tipsTextview.setText(R.string.pull_to_refresh_refreshing_label); |
255 | lastUpdatedTextView.setVisibility(View.GONE); |
256 |
257 | //Log.v(TAG, "当前状态,正在刷新..."); |
258 | break ; |
259 | case DONE: |
260 | //System.out.println("完成DONE-TopPad:"+(-1 * headContentHeight)); |
261 | headView.setPadding(headView.getPaddingLeft(), - 1 * headContentHeight, headView.getPaddingRight(), headView.getPaddingBottom()); |
262 | headView.invalidate(); |
263 |
264 | progressBar.setVisibility(View.GONE); |
265 | arrowImageView.clearAnimation(); |
266 | // 此处更换图标 |
267 | arrowImageView.setImageResource(R.drawable.ic_pulltorefresh_arrow); |
268 |
269 | tipsTextview.setText(R.string.pull_to_refresh_pull_label); |
270 | lastUpdatedTextView.setVisibility(View.VISIBLE); |
271 |
272 | //Log.v(TAG, "当前状态,done"); |
273 | break ; |
274 | } |
275 | } |
276 |
277 | //点击刷新 |
278 | public void clickRefresh() { |
279 | setSelection( 0 ); |
280 | state = REFRESHING; |
281 | changeHeaderViewByState(); |
282 | onRefresh(); |
283 | } |
284 |
285 | public void setOnRefreshListener(OnRefreshListener refreshListener) { |
286 | this .refreshListener = refreshListener; |
287 | } |
288 |
289 | public interface OnRefreshListener { |
290 | public void onRefresh(); |
291 | } |
292 |
293 | public void onRefreshComplete(String update) { |
294 | lastUpdatedTextView.setText(update); |
295 | onRefreshComplete(); |
296 | } |
297 |
298 | public void onRefreshComplete() { |
299 | state = DONE; |
300 | changeHeaderViewByState(); |
301 | } |
302 |
303 | private void onRefresh() { |
304 | if (refreshListener != null ) { |
305 | refreshListener.onRefresh(); |
306 | } |
307 | } |
308 |
309 | // 计算headView的width及height值 |
310 | private void measureView(View child) { |
311 | ViewGroup.LayoutParams p = child.getLayoutParams(); |
312 | if (p == null ) { |
313 | p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, |
314 | ViewGroup.LayoutParams.WRAP_CONTENT); |
315 | } |
316 | int childWidthSpec = ViewGroup.getChildMeasureSpec( 0 , 0 + 0 , p.width); |
317 | int lpHeight = p.height; |
318 | int childHeightSpec; |
319 | if (lpHeight > 0 ) { |
320 | childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, |
321 | MeasureSpec.EXACTLY); |
322 | } else { |
323 | childHeightSpec = MeasureSpec.makeMeasureSpec( 0 , |
324 | MeasureSpec.UNSPECIFIED); |
325 | } |
326 | child.measure(childWidthSpec, childHeightSpec); |
327 | } |
328 |
329 | } |
里面的注释可以说写的相当的详细,我这里就不详细解释了,大家可以直接复制到项目中去看,下面我们来看看,如何使用它:
01 | pullrefresh_lv = (PullToRefreshListView) findViewById(R.id.pullrefresh_lv); |
02 | testArr = new LinkedList<String>(); |
03 | testArr.add( "↓↓请下拉我看效果↓↓" ); |
04 | testAdapter = new TestAdapter(testArr, this ); |
05 | pullrefresh_lv.setAdapter(testAdapter); |
06 |
07 | pullrefresh_lv |
08 | .setOnRefreshListener( new PullToRefreshListView.OnRefreshListener() { |
09 | public void onRefresh() { |
10 | // 这里进行网络操作/数据库操作/文件操作 |
11 | handler.sendEmptyMessageDelayed( 0 , 2000 ); |
12 | } |
13 | }); |
使用起来也非常方便,和原生态listview无太多差别,只是多了一个 setOnRefreshListener方法,对的,这里就是处理刷新动作的地方,我这里没有进行网络操作,只是做了一个假数据演示(延迟刷新),下面来看看Handler里面做的处理:
01 | Handler handler = new Handler() { |
02 | public void handleMessage(android.os.Message msg) { |
03 | switch (msg.what) { |
04 | case 0 : |
05 | i++; |
06 | testArr.addFirst( "刷新第" + i + "条" ); |
07 | pullrefresh_lv |
08 | .onRefreshComplete(getString(R.string.pull_to_refresh_update) |
09 | + new Date().toLocaleString()); |
10 | pullrefresh_lv.onRefreshComplete(); |
11 | testAdapter.notifyDataSetChanged(); |
12 | break ; |
13 |
14 | default : |
15 | break ; |
16 | } |
17 | }; |
18 | }; |
这里当我们刷新动作完成后,调用onRefreshComplete()结束该动作,到这里,整个下来刷新的操作流程就讲完了。
因本站存储空间有限,稍后将源码上传其它社区,有问题请留言,感谢大家。
代码已上传,附上链接:http://www.eoeandroid.com/forum.php?mod=viewthread&tid=304996&page=1&extra=#pid3147702