Android listview仿新浪微博下拉刷新例子,其实所有此功能最早是国外实现的,现在网上能看到的都是基于这个例子,只是有些细节不同。算法上确实比较巧妙,直得学习一下。
AndroidManifest.xml
01 | <? xml version = "1.0" encoding = "utf-8" ?> |
02 | < manifest xmlns:android = "http://schemas.android.com/apk/res/android" |
03 | package = "cn.com.karl.list" |
04 | android:versionCode = "1" |
05 | android:versionName = "1.0" > |
07 | < uses-sdk android:minSdkVersion = "4" /> |
10 | android:icon = "@drawable/ic_launcher" |
11 | android:label = "@string/app_name" > |
13 | android:label = "@string/app_name" |
14 | android:name = ".MainActivity" > |
16 | < action android:name = "android.intent.action.MAIN" /> |
18 | < category android:name = "android.intent.category.LAUNCHER" /> |
main..xml
01 | <? xml version = "1.0" encoding = "utf-8" ?> |
02 | < LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android" |
03 | android:layout_width = "fill_parent" |
04 | android:layout_height = "fill_parent" |
05 | android:orientation = "vertical" > |
07 | < cn.com.karl.list.MyListView |
08 | android:layout_width = "fill_parent" |
09 | android:layout_height = "fill_parent" |
10 | android:id = "@+id/listView" |
head.xml
01 | <? xml version = "1.0" encoding = "utf-8" ?> |
06 | xmlns:android = "http://schemas.android.com/apk/res/android" |
07 | android:layout_width = "fill_parent" |
08 | android:layout_height = "wrap_content" |
09 | android:background = "#ffffff" |
14 | android:layout_width = "fill_parent" |
15 | android:layout_height = "wrap_content" |
16 | android:id = "@+id/head_contentLayout" |
17 | android:paddingLeft = "30dp" |
22 | android:layout_width = "wrap_content" |
23 | android:layout_height = "wrap_content" |
24 | android:layout_alignParentLeft = "true" |
25 | android:layout_centerVertical = "true" |
30 | android:layout_width = "wrap_content" |
31 | android:layout_height = "wrap_content" |
32 | android:layout_gravity = "center" |
33 | android:src = "@drawable/arrow_down" |
34 | android:id = "@+id/head_arrowImageView" |
39 | android:layout_width = "wrap_content" |
40 | android:layout_height = "wrap_content" |
41 | style = "?android:attr/progressBarStyleSmall" |
42 | android:layout_gravity = "center" |
43 | android:id = "@+id/head_progressBar" |
45 | android:visibility = "gone" |
52 | android:layout_width = "wrap_content" |
53 | android:layout_height = "wrap_content" |
54 | android:layout_centerHorizontal = "true" |
55 | android:orientation = "vertical" |
56 | android:gravity = "center_horizontal" |
61 | android:layout_width = "wrap_content" |
62 | android:layout_height = "wrap_content" |
64 | android:textSize = "15dp" |
65 | android:id = "@+id/head_tipsTextView" |
70 | android:layout_width = "wrap_content" |
71 | android:layout_height = "wrap_content" |
72 | android:id = "@+id/head_lastUpdatedTextView" |
74 | android:textSize = "12dp" |
item.xml
01 | <? xml version = "1.0" encoding = "utf-8" ?> |
02 | < LinearLayout xmlns:android = "http://schemas.android.com/apk/res/android" |
03 | android:layout_width = "match_parent" |
04 | android:layout_height = "wrap_content" |
05 | android:gravity = "center_vertical" |
06 | android:background = "#ffffff" |
07 | android:orientation = "horizontal" > |
10 | android:id = "@+id/imageView_item" |
11 | android:layout_width = "wrap_content" |
12 | android:layout_height = "wrap_content" |
13 | android:layout_marginLeft = "10dp" |
14 | android:src = "@drawable/ic_launcher" /> |
17 | android:id = "@+id/textView_item" |
18 | android:layout_width = "wrap_content" |
19 | android:layout_height = "wrap_content" |
20 | android:layout_marginLeft = "10dp" |
21 | android:text = "TextView" /> |
MainActivity.java
001 | package cn.com.karl.list; |
004 | import java.util.LinkedList; |
006 | import cn.com.karl.list.MyListView.OnRefreshListener; |
008 | import android.app.Activity; |
009 | import android.content.res.Configuration; |
010 | import android.os.AsyncTask; |
011 | import android.os.Bundle; |
012 | import android.util.DisplayMetrics; |
013 | import android.util.Log; |
014 | import android.view.LayoutInflater; |
015 | import android.view.View; |
016 | import android.view.ViewGroup; |
017 | import android.view.WindowManager; |
018 | import android.widget.BaseAdapter; |
019 | import android.widget.TextView; |
021 | public class MainActivity extends Activity |
024 | private LinkedList<String> data; |
025 | private BaseAdapter adapter; |
026 | private MyListView listView; |
028 | public void onCreate(Bundle savedInstanceState) |
030 | super .onCreate(savedInstanceState); |
031 | setContentView(R.layout.main); |
036 | data = new LinkedList<String>(); |
037 | for ( int i= 0 ;i< 10 ;i++) |
039 | data.add(String.valueOf(i)); |
042 | listView = (MyListView) findViewById(R.id.listView); |
044 | adapter = new BaseAdapter() |
046 | public View getView( int position, View convertView, ViewGroup parent) |
048 | convertView = LayoutInflater.from(getApplicationContext()).inflate(R.layout.item, null ); |
049 | TextView textView = (TextView) convertView.findViewById(R.id.textView_item); |
050 | textView.setText(data.get(position)); |
054 | public long getItemId( int position) |
059 | public Object getItem( int position) |
061 | return data.get(position); |
064 | public int getCount() |
070 | listView.setAdapter(adapter); |
072 | listView.setOnRefreshListener( new OnRefreshListener() |
074 | public void onRefresh() |
076 | RefreshTask rTask = new RefreshTask(); |
083 | class RefreshTask extends AsyncTask<Integer, Integer, String> |
086 | protected String doInBackground(Integer... params) |
090 | Thread.sleep(params[ 0 ]); |
097 | data.addFirst( "刷新后的内容" ); |
102 | protected void onPostExecute(String result) |
104 | super .onPostExecute(result); |
105 | adapter.notifyDataSetChanged(); |
106 | listView.onRefreshComplete(); |
111 | public void displayScreenSize() |
114 | if ( this .getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE) |
116 | setTitle( "landscape" ); |
119 | if ( this .getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT) |
121 | setTitle( "portrait" ); |
125 | WindowManager manager = getWindowManager(); |
126 | int width = manager.getDefaultDisplay().getWidth(); |
127 | int height = manager.getDefaultDisplay().getHeight(); |
129 | Log.v( "am10" , "width: " + width + " height: " + height); |
132 | DisplayMetrics dMetrics = new DisplayMetrics(); |
133 | getWindowManager().getDefaultDisplay().getMetrics(dMetrics); |
134 | int screenWidth = dMetrics.widthPixels; |
135 | int screenHeight = dMetrics.heightPixels; |
137 | Log.v( "am10" , "screenWidth: " + screenWidth + " screenHeight: " + screenHeight); |
MyListView.java
001 | package cn.com.karl.list; |
003 | import java.text.SimpleDateFormat; |
004 | import java.util.Date; |
006 | import android.content.Context; |
007 | import android.util.AttributeSet; |
008 | import android.util.Log; |
009 | import android.view.LayoutInflater; |
010 | import android.view.MotionEvent; |
011 | import android.view.View; |
012 | import android.view.ViewGroup; |
013 | import android.view.animation.LinearInterpolator; |
014 | import android.view.animation.RotateAnimation; |
015 | import android.widget.AbsListView; |
016 | import android.widget.BaseAdapter; |
017 | import android.widget.ImageView; |
018 | import android.widget.LinearLayout; |
019 | import android.widget.ListView; |
020 | import android.widget.AbsListView.OnScrollListener; |
022 | import android.widget.TextView; |
024 | public class MyListView extends ListView implements OnScrollListener |
026 | private static final String TAG = "am10" ; |
027 | private final static int RELEASE_To_REFRESH = 0 ; |
028 | private final static int PULL_To_REFRESH = 1 ; |
029 | private final static int REFRESHING = 2 ; |
030 | private final static int DONE = 3 ; |
031 | private final static int LOADING = 4 ; |
034 | private final static int RATIO = 3 ; |
036 | private LayoutInflater inflater; |
037 | private LinearLayout headView; |
038 | private TextView tipsTextview; |
039 | private TextView lastUpdatedTextView; |
040 | private ImageView arrowImageView; |
043 | private RotateAnimation animation; |
044 | private RotateAnimation reverseAnimation; |
047 | private boolean isRecored; |
050 | private int firstItemIndex; |
052 | private int headContentWidth; |
053 | private int headContentHeight; |
057 | private boolean isBack; |
059 | private OnRefreshListener refreshListener; |
061 | private boolean isRefreshable; |
063 | public MyListView(Context context) |
069 | public MyListView(Context context, AttributeSet attrs) |
071 | super (context, attrs); |
075 | private void init(Context context) |
078 | inflater = LayoutInflater.from(context); |
080 | headView = (LinearLayout) inflater.inflate(R.layout.head, null ); |
082 | arrowImageView = (ImageView) headView.findViewById(R.id.head_arrowImageView); |
083 | arrowImageView.setMinimumWidth( 70 ); |
084 | arrowImageView.setMinimumHeight( 50 ); |
087 | tipsTextview = (TextView) headView.findViewById(R.id.head_tipsTextView); |
088 | lastUpdatedTextView = (TextView) headView.findViewById(R.id.head_lastUpdatedTextView); |
091 | measureView(headView); |
092 | headContentHeight = headView.getMeasuredHeight(); |
093 | headContentWidth = headView.getMeasuredWidth(); |
095 | headView.setPadding( 0 , - 1 * headContentHeight, 0 , 0 ); |
096 | headView.invalidate(); |
098 | Log.v(TAG, "width:" + headContentWidth + " height:" + headContentHeight); |
100 | addHeaderView(headView, null , false ); |
101 | setOnScrollListener( this ); |
103 | animation = new RotateAnimation( 0 , - 180 , |
104 | RotateAnimation.RELATIVE_TO_SELF, 0 .5f, |
105 | RotateAnimation.RELATIVE_TO_SELF, 0 .5f); |
106 | animation.setInterpolator( new LinearInterpolator()); |
107 | animation.setDuration( 250 ); |
108 | animation.setFillAfter( true ); |
110 | reverseAnimation = new RotateAnimation(- 180 , 0 , |
111 | RotateAnimation.RELATIVE_TO_SELF, 0 .5f, |
112 | RotateAnimation.RELATIVE_TO_SELF, 0 .5f); |
113 | reverseAnimation.setInterpolator( new LinearInterpolator()); |
114 | reverseAnimation.setDuration( 200 ); |
115 | reverseAnimation.setFillAfter( true ); |
118 | isRefreshable = false ; |
121 | public void onScroll(AbsListView arg0, int firstVisiableItem, int arg2, int arg3) |
123 | firstItemIndex = firstVisiableItem; |
127 | public void onScrollStateChanged(AbsListView arg0, int arg1) |
131 | public boolean onTouchEvent(MotionEvent event) |
136 | switch (event.getAction()) |
138 | case MotionEvent.ACTION_DOWN: |
139 | if (firstItemIndex == 0 && !isRecored) |
144 | startY = ( int ) event.getY(); |
145 | Log.v(TAG, "在down时候记录当前位置" + " startY:" +startY); |
149 | case MotionEvent.ACTION_UP: |
150 | if (state != REFRESHING && state != LOADING) |
157 | if (state == PULL_To_REFRESH) |
160 | changeHeaderViewByState(); |
161 | Log.v(TAG, "由下拉刷新状态,到done状态" ); |
164 | if (state == RELEASE_To_REFRESH) |
167 | changeHeaderViewByState(); |
169 | Log.v(TAG, "由松开刷新状态,到done状态" ); |
177 | case MotionEvent.ACTION_MOVE: |
178 | int tempY = ( int ) event.getY(); |
182 | * 手指移动过程中tempY数据会不断变化,当滑动到firstItemIndex,即到达顶部, |
183 | * 需要记录手指所在屏幕的位置: startY = tempY ,后面作位置比较使用 |
185 | * 如果手指继续向下推,tempY继续变化,当tempY-startY>0,即是需要显示header部分 |
187 | * 此时需要更改状态:state = PULL_To_REFRESH |
189 | if (!isRecored && firstItemIndex == 0 ) |
193 | Log.v(TAG, "在move时候记录下位置" + " startY:" +startY); |
196 | if (state != REFRESHING && isRecored && state != LOADING) |
199 | * 保证在设置padding的过程中,当前的位置一直是在head, |
200 | * 否则如果当列表超出屏幕的话,当在上推的时候,列表会同时进行滚动 |
204 | if (state == RELEASE_To_REFRESH) |
209 | if (((tempY - startY) / RATIO < headContentHeight) && (tempY - startY) > 0 ) |
211 | state = PULL_To_REFRESH; |
212 | changeHeaderViewByState(); |
213 | Log.v(TAG, "由松开刷新状态转变到下拉刷新状态" ); |
217 | else if (tempY - startY <= 0 ) |
220 | changeHeaderViewByState(); |
221 | Log.v(TAG, "---由松开刷新状态转变到done状态" ); |
231 | if (state == PULL_To_REFRESH) |
236 | * 下拉到可以进入RELEASE_TO_REFRESH的状态 |
238 | * 等于headContentHeight时,即是正好完全显示header部分 |
239 | * 大于headContentHeight时,即是超出header部分更多 |
241 | * 当header部分能够完全显示或者超出显示, |
242 | * 需要更改状态: state = RELEASE_To_REFRESH |
244 | if ((tempY - startY) / RATIO >= headContentHeight) |
246 | state = RELEASE_To_REFRESH; |
248 | changeHeaderViewByState(); |
249 | Log.v(TAG, "由done或者下拉刷新状态转变到松开刷新" ); |
253 | else if (tempY - startY <= 0 ) |
256 | changeHeaderViewByState(); |
257 | Log.v(TAG, "由done或者下拉刷新状态转变到done状态" ); |
264 | if (tempY - startY > 0 ) |
267 | * 手指移动过程中tempY数据会不断变化,当滑动到firstItemIndex,即到达顶部, |
268 | * 需要记录手指所在屏幕的位置: startY = tempY ,后面作位置比较使用 |
270 | * 如果手指继续向下推,tempY继续变化,当tempY-startY>0,即是需要显示header部分 |
272 | * 此时需要更改状态:state = PULL_To_REFRESH |
275 | state = PULL_To_REFRESH; |
276 | changeHeaderViewByState(); |
281 | if (state == PULL_To_REFRESH) |
284 | headView.setPadding( 0 , - 1 * headContentHeight + (tempY - startY) / RATIO, 0 , 0 ); |
288 | if (state == RELEASE_To_REFRESH) |
290 | headView.setPadding( 0 , (tempY - startY) / RATIO - headContentHeight, 0 , 0 ); |
296 | return super .onTouchEvent(event); |
300 | private void changeHeaderViewByState() |
304 | case RELEASE_To_REFRESH: |
305 | arrowImageView.setVisibility(View.VISIBLE); |
307 | tipsTextview.setVisibility(View.VISIBLE); |
308 | lastUpdatedTextView.setVisibility(View.VISIBLE); |
310 | arrowImageView.clearAnimation(); |
311 | arrowImageView.startAnimation(animation); |
313 | tipsTextview.setText( "松开刷新" ); |
315 | Log.v(TAG, "当前状态,松开刷新" ); |
318 | case PULL_To_REFRESH: |
320 | tipsTextview.setVisibility(View.VISIBLE); |
321 | lastUpdatedTextView.setVisibility(View.VISIBLE); |
322 | arrowImageView.clearAnimation(); |
323 | arrowImageView.setVisibility(View.VISIBLE); |
326 | * 是否向下滑回,是由RELEASE_To_REFRESH状态转变来的 |
331 | arrowImageView.clearAnimation(); |
332 | arrowImageView.startAnimation(reverseAnimation); |
333 | tipsTextview.setText( "下拉刷新" ); |
338 | tipsTextview.setText( "下拉刷新" ); |
342 | Log.v(TAG, "当前状态,下拉刷新" ); |
346 | Log.v(TAG, "REFRESHING..." ); |
347 | headView.setPadding( 0 , 0 , 0 , 0 ); |
350 | arrowImageView.clearAnimation(); |
351 | arrowImageView.setVisibility(View.GONE); |
352 | tipsTextview.setText( "正在刷新..." ); |
353 | lastUpdatedTextView.setVisibility(View.VISIBLE); |
355 | Log.v(TAG, "当前状态,正在刷新..." ); |
359 | headView.setPadding( 0 , - 1 * headContentHeight, 0 , 0 ); |
362 | arrowImageView.clearAnimation(); |
363 | arrowImageView.setImageResource(R.drawable.arrow_down); |
364 | tipsTextview.setText( "下拉刷新" ); |
365 | lastUpdatedTextView.setVisibility(View.VISIBLE); |
367 | Log.v(TAG, "当前状态,done" ); |
372 | public void setOnRefreshListener(OnRefreshListener refreshListener) |
374 | this .refreshListener = refreshListener; |
375 | isRefreshable = true ; |
378 | public interface OnRefreshListener |
380 | public void onRefresh(); |
383 | public void onRefreshComplete() |
386 | SimpleDateFormat format = new SimpleDateFormat( "yyyy年MM月dd日 HH:mm" ); |
387 | String date = format.format( new Date()); |
388 | lastUpdatedTextView.setText( "最近更新:" + date); |
389 | changeHeaderViewByState(); |
392 | private void onRefresh() |
394 | if (refreshListener != null ) |
396 | refreshListener.onRefresh(); |
401 | private void measureView(View child) |
403 | ViewGroup.LayoutParams p = child.getLayoutParams(); |
407 | p = new ViewGroup.LayoutParams( |
408 | ViewGroup.LayoutParams.FILL_PARENT, |
409 | ViewGroup.LayoutParams.WRAP_CONTENT); |
412 | int childWidthSpec = ViewGroup.getChildMeasureSpec( 0 , 0 + 0 , p.width); |
413 | int lpHeight = p.height; |
418 | childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY); |
422 | childHeightSpec = MeasureSpec.makeMeasureSpec( 0 , MeasureSpec.UNSPECIFIED); |
425 | child.measure(childWidthSpec, childHeightSpec); |
428 | public void setAdapter(BaseAdapter adapter) |
430 | SimpleDateFormat format= new SimpleDateFormat( "yyyy年MM月dd日 HH:mm" ); |
431 | String date=format.format( new Date()); |
432 | lastUpdatedTextView.setText( "最近更新:" + date); |
433 | super .setAdapter(adapter); |
这个例子忘记那里下载的,这里也尊重作者,注明并不是我本人写的。如果谁知道原作者资料请告知,在此添加。
我修改了一下代码,使用AsyncTask异步任务更新数据,还有添加和修改了一些注释。