轉載請注明出處:http://blog.csdn.net/guolin_blog/article/details/9255575
最 近項目中需要用到ListView下拉刷新的功能,一開始想圖省事,在網(wǎng)上直接找一個現(xiàn)成的,可是嘗試了網(wǎng)上多個版本的下拉刷新之后發(fā)現(xiàn)效果都不怎么理 想。有些是因為功能不完整或有Bug,有些是因為使用起來太復雜,十全十美的還真沒找到。因此我也是放棄了在網(wǎng)上找現(xiàn)成代碼的想法,自己花功夫編寫了一種 非常簡單的下拉刷新實現(xiàn)方案,現(xiàn)在拿出來和大家分享一下。相信在閱讀完本篇文章之后,大家都可以在自己的項目中一分鐘引入下拉刷新功能。
首 先講一下實現(xiàn)原理。這里我們將采取的方案是使用組合View的方式,先自定義一個布局繼承自LinearLayout,然后在這個布局中加入下拉頭和 ListView這兩個子元素,并讓這兩個子元素縱向排列。初始化的時候,讓下拉頭向上偏移出屏幕,這樣我們看到的就只有ListView了。然后對 ListView的touch事件進行監(jiān)聽,如果當前ListView已經(jīng)滾動到頂部并且手指還在向下拉的話,那就將下拉頭顯示出來,松手后進行刷新操 作,并將下拉頭隱藏。原理示意圖如下:
? ? ? ? ? ? ? ? ? ? ? ? ? ??
那我們現(xiàn)在就來動手實現(xiàn)一下,新建一個項目起名叫PullToRefreshTest,先在項目中定義一個下拉頭的布局文件pull_to_refresh.xml,代碼如下所示:
[html] view plaincopy
<RelativeLayout?xmlns:android="http://schemas.android.com/apk/res/android"??????xmlns:tools="http://schemas.android.com/tools"??????android:id="@+id/pull_to_refresh_head"??????android:layout_width="fill_parent"??????android:layout_height="60dip"?>????????<LinearLayout??????????android:layout_width="200dip"??????????android:layout_height="60dip"??????????android:layout_centerInParent="true"??????????android:orientation="horizontal"?>????????????<RelativeLayout??????????????android:layout_width="0dip"??????????????android:layout_height="60dip"??????????????android:layout_weight="3"??????????????>??????????????<ImageView???????????????????android:id="@+id/arrow"??????????????????android:layout_width="wrap_content"??????????????????android:layout_height="wrap_content"??????????????????android:layout_centerInParent="true"??????????????????android:src="@drawable/arrow"??????????????????/>??????????????<ProgressBar???????????????????android:id="@+id/progress_bar"??????????????????android:layout_width="30dip"??????????????????android:layout_height="30dip"??????????????????android:layout_centerInParent="true"??????????????????android:visibility="gone"??????????????????/>??????????</RelativeLayout>????????????<LinearLayout??????????????android:layout_width="0dip"??????????????android:layout_height="60dip"??????????????android:layout_weight="12"??????????????android:orientation="vertical"?>????????????????<TextView??????????????????android:id="@+id/description"??????????????????android:layout_width="fill_parent"??????????????????android:layout_height="0dip"??????????????????android:layout_weight="1"??????????????????android:gravity="center_horizontal|bottom"??????????????????android:text="@string/pull_to_refresh"?/>????????????????<TextView??????????????????android:id="@+id/updated_at"??????????????????android:layout_width="fill_parent"??????????????????android:layout_height="0dip"??????????????????android:layout_weight="1"??????????????????android:gravity="center_horizontal|top"??????????????????android:text="@string/updated_at"?/>??????????</LinearLayout>??????</LinearLayout>????</RelativeLayout>?? 在這個布局中,我們包含了一個下拉指示箭頭,一個下拉狀態(tài)文字提示,和一個上次更新的時間。當然,還有一個隱藏的旋轉進度條,只有正在刷新的時候我們才會將它顯示出來。
布局中所有引用的字符串我們都放在strings.xml中,如下所示:
[html] view plaincopy
<?xml?version="1.0"?encoding="utf-8"?>??<resources>????????<string?name="app_name">PullToRefreshTest</string>??????<string?name="pull_to_refresh">下拉可以刷新</string>??????<string?name="release_to_refresh">釋放立即刷新</string>??????<string?name="refreshing">正在刷新…</string>??????<string?name="not_updated_yet">暫未更新過</string>??????<string?name="updated_at">上次更新于%1$s前</string>??????<string?name="updated_just_now">剛剛更新</string>??????<string?name="time_error">時間有問題</string>????????</resources>?? 然后新建一個RefreshableView繼承自LinearLayout,代碼如下所示:
[java] view plaincopy
public?class?RefreshableView?extends?LinearLayout?implements?OnTouchListener?{????????????????public?static?final?int?STATUS_PULL_TO_REFRESH?=?0;????????????????public?static?final?int?STATUS_RELEASE_TO_REFRESH?=?1;????????????????public?static?final?int?STATUS_REFRESHING?=?2;????????????????public?static?final?int?STATUS_REFRESH_FINISHED?=?3;????????????????public?static?final?int?SCROLL_SPEED?=?-20;????????????????public?static?final?long?ONE_MINUTE?=?60?*?1000;????????????????public?static?final?long?ONE_HOUR?=?60?*?ONE_MINUTE;????????????????public?static?final?long?ONE_DAY?=?24?*?ONE_HOUR;????????????????public?static?final?long?ONE_MONTH?=?30?*?ONE_DAY;????????????????public?static?final?long?ONE_YEAR?=?12?*?ONE_MONTH;????????????????private?static?final?String?UPDATED_AT?=?"updated_at";????????????????private?PullToRefreshListener?mListener;????????????????private?SharedPreferences?preferences;????????????????private?View?header;????????????????private?ListView?listView;????????????????private?ProgressBar?progressBar;????????????????private?ImageView?arrow;????????????????private?TextView?description;????????????????private?TextView?updateAt;????????????????private?MarginLayoutParams?headerLayoutParams;????????????????private?long?lastUpdateTime;????????????????private?int?mId?=?-1;????????????????private?int?hideHeaderHeight;?????????????????private?int?currentStatus?=?STATUS_REFRESH_FINISHED;;????????????????private?int?lastStatus?=?currentStatus;????????????????private?float?yDown;????????????????private?int?touchSlop;????????????????private?boolean?loadOnce;????????????????private?boolean?ableToPull;???????????????????public?RefreshableView(Context?context,?AttributeSet?attrs)?{??????????super(context,?attrs);??????????preferences?=?PreferenceManager.getDefaultSharedPreferences(context);??????????header?=?LayoutInflater.from(context).inflate(R.layout.pull_to_refresh,?null,?true);??????????progressBar?=?(ProgressBar)?header.findViewById(R.id.progress_bar);??????????arrow?=?(ImageView)?header.findViewById(R.id.arrow);??????????description?=?(TextView)?header.findViewById(R.id.description);??????????updateAt?=?(TextView)?header.findViewById(R.id.updated_at);??????????touchSlop?=?ViewConfiguration.get(context).getScaledTouchSlop();??????????refreshUpdatedAtValue();??????????setOrientation(VERTICAL);??????????addView(header,?0);??????}????????????????@Override??????protected?void?onLayout(boolean?changed,?int?l,?int?t,?int?r,?int?b)?{??????????super.onLayout(changed,?l,?t,?r,?b);??????????if?(changed?&&?!loadOnce)?{??????????????hideHeaderHeight?=?-header.getHeight();??????????????headerLayoutParams?=?(MarginLayoutParams)?header.getLayoutParams();??????????????headerLayoutParams.topMargin?=?hideHeaderHeight;??????????????listView?=?(ListView)?getChildAt(1);??????????????listView.setOnTouchListener(this);??????????????loadOnce?=?true;??????????}??????}????????????????@Override??????public?boolean?onTouch(View?v,?MotionEvent?event)?{??????????setIsAbleToPull(event);??????????if?(ableToPull)?{??????????????switch?(event.getAction())?{??????????????case?MotionEvent.ACTION_DOWN:??????????????????yDown?=?event.getRawY();??????????????????break;??????????????case?MotionEvent.ACTION_MOVE:??????????????????float?yMove?=?event.getRawY();??????????????????int?distance?=?(int)?(yMove?-?yDown);????????????????????????????????????if?(distance?<=?0?&&?headerLayoutParams.topMargin?<=?hideHeaderHeight)?{??????????????????????return?false;??????????????????}??????????????????if?(distance?<?touchSlop)?{??????????????????????return?false;??????????????????}??????????????????if?(currentStatus?!=?STATUS_REFRESHING)?{??????????????????????if?(headerLayoutParams.topMargin?>?0)?{??????????????????????????currentStatus?=?STATUS_RELEASE_TO_REFRESH;??????????????????????}?else?{??????????????????????????currentStatus?=?STATUS_PULL_TO_REFRESH;??????????????????????}????????????????????????????????????????????headerLayoutParams.topMargin?=?(distance?/?2)?+?hideHeaderHeight;??????????????????????header.setLayoutParams(headerLayoutParams);??????????????????}??????????????????break;??????????????case?MotionEvent.ACTION_UP:??????????????default:??????????????????if?(currentStatus?==?STATUS_RELEASE_TO_REFRESH)?{????????????????????????????????????????????new?RefreshingTask().execute();??????????????????}?else?if?(currentStatus?==?STATUS_PULL_TO_REFRESH)?{????????????????????????????????????????????new?HideHeaderTask().execute();??????????????????}??????????????????break;??????????????}????????????????????????????if?(currentStatus?==?STATUS_PULL_TO_REFRESH??????????????????????||?currentStatus?==?STATUS_RELEASE_TO_REFRESH)?{??????????????????updateHeaderView();????????????????????????????????????listView.setPressed(false);??????????????????listView.setFocusable(false);??????????????????listView.setFocusableInTouchMode(false);??????????????????lastStatus?=?currentStatus;????????????????????????????????????return?true;??????????????}??????????}??????????return?false;??????}?????????????????????public?void?setOnRefreshListener(PullToRefreshListener?listener,?int?id)?{??????????mListener?=?listener;??????????mId?=?id;??????}????????????????public?void?finishRefreshing()?{??????????currentStatus?=?STATUS_REFRESH_FINISHED;??????????preferences.edit().putLong(UPDATED_AT?+?mId,?System.currentTimeMillis()).commit();??????????new?HideHeaderTask().execute();??????}???????????????????private?void?setIsAbleToPull(MotionEvent?event)?{??????????View?firstChild?=?listView.getChildAt(0);??????????if?(firstChild?!=?null)?{??????????????int?firstVisiblePos?=?listView.getFirstVisiblePosition();??????????????if?(firstVisiblePos?==?0?&&?firstChild.getTop()?==?0)?{??????????????????if?(!ableToPull)?{??????????????????????yDown?=?event.getRawY();??????????????????}????????????????????????????????????ableToPull?=?true;??????????????}?else?{??????????????????if?(headerLayoutParams.topMargin?!=?hideHeaderHeight)?{??????????????????????headerLayoutParams.topMargin?=?hideHeaderHeight;??????????????????????header.setLayoutParams(headerLayoutParams);??????????????????}??????????????????ableToPull?=?false;??????????????}??????????}?else?{????????????????????????????ableToPull?=?true;??????????}??????}????????????????private?void?updateHeaderView()?{??????????if?(lastStatus?!=?currentStatus)?{??????????????if?(currentStatus?==?STATUS_PULL_TO_REFRESH)?{??????????????????description.setText(getResources().getString(R.string.pull_to_refresh));??????????????????arrow.setVisibility(View.VISIBLE);??????????????????progressBar.setVisibility(View.GONE);??????????????????rotateArrow();??????????????}?else?if?(currentStatus?==?STATUS_RELEASE_TO_REFRESH)?{??????????????????description.setText(getResources().getString(R.string.release_to_refresh));??????????????????arrow.setVisibility(View.VISIBLE);??????????????????progressBar.setVisibility(View.GONE);??????????????????rotateArrow();??????????????}?else?if?(currentStatus?==?STATUS_REFRESHING)?{??????????????????description.setText(getResources().getString(R.string.refreshing));??????????????????progressBar.setVisibility(View.VISIBLE);??????????????????arrow.clearAnimation();??????????????????arrow.setVisibility(View.GONE);??????????????}??????????????refreshUpdatedAtValue();??????????}??????}????????????????private?void?rotateArrow()?{??????????float?pivotX?=?arrow.getWidth()?/?2f;??????????float?pivotY?=?arrow.getHeight()?/?2f;??????????float?fromDegrees?=?0f;??????????float?toDegrees?=?0f;??????????if?(currentStatus?==?STATUS_PULL_TO_REFRESH)?{??????????????fromDegrees?=?180f;??????????????toDegrees?=?360f;??????????}?else?if?(currentStatus?==?STATUS_RELEASE_TO_REFRESH)?{??????????????fromDegrees?=?0f;??????????????toDegrees?=?180f;??????????}??????????RotateAnimation?animation?=?new?RotateAnimation(fromDegrees,?toDegrees,?pivotX,?pivotY);??????????animation.setDuration(100);??????????animation.setFillAfter(true);??????????arrow.startAnimation(animation);??????}????????????????private?void?refreshUpdatedAtValue()?{??????????lastUpdateTime?=?preferences.getLong(UPDATED_AT?+?mId,?-1);??????????long?currentTime?=?System.currentTimeMillis();??????????long?timePassed?=?currentTime?-?lastUpdateTime;??????????long?timeIntoFormat;??????????String?updateAtValue;??????????if?(lastUpdateTime?==?-1)?{??????????????updateAtValue?=?getResources().getString(R.string.not_updated_yet);??????????}?else?if?(timePassed?<?0)?{??????????????updateAtValue?=?getResources().getString(R.string.time_error);??????????}?else?if?(timePassed?<?ONE_MINUTE)?{??????????????updateAtValue?=?getResources().getString(R.string.updated_just_now);??????????}?else?if?(timePassed?<?ONE_HOUR)?{??????????????timeIntoFormat?=?timePassed?/?ONE_MINUTE;??????????????String?value?=?timeIntoFormat?+?"分鐘";??????????????updateAtValue?=?String.format(getResources().getString(R.string.updated_at),?value);??????????}?else?if?(timePassed?<?ONE_DAY)?{??????????????timeIntoFormat?=?timePassed?/?ONE_HOUR;??????????????String?value?=?timeIntoFormat?+?"小時";??????????????updateAtValue?=?String.format(getResources().getString(R.string.updated_at),?value);??????????}?else?if?(timePassed?<?ONE_MONTH)?{??????????????timeIntoFormat?=?timePassed?/?ONE_DAY;??????????????String?value?=?timeIntoFormat?+?"天";??????????????updateAtValue?=?String.format(getResources().getString(R.string.updated_at),?value);??????????}?else?if?(timePassed?<?ONE_YEAR)?{??????????????timeIntoFormat?=?timePassed?/?ONE_MONTH;??????????????String?value?=?timeIntoFormat?+?"個月";??????????????updateAtValue?=?String.format(getResources().getString(R.string.updated_at),?value);??????????}?else?{??????????????timeIntoFormat?=?timePassed?/?ONE_YEAR;??????????????String?value?=?timeIntoFormat?+?"年";??????????????updateAtValue?=?String.format(getResources().getString(R.string.updated_at),?value);??????????}??????????updateAt.setText(updateAtValue);??????}??????????????????class?RefreshingTask?extends?AsyncTask<Void,?Integer,?Void>?{????????????@Override??????????protected?Void?doInBackground(Void...?params)?{??????????????int?topMargin?=?headerLayoutParams.topMargin;??????????????while?(true)?{??????????????????topMargin?=?topMargin?+?SCROLL_SPEED;??????????????????if?(topMargin?<=?0)?{??????????????????????topMargin?=?0;??????????????????????break;??????????????????}??????????????????publishProgress(topMargin);??????????????????sleep(10);??????????????}??????????????currentStatus?=?STATUS_REFRESHING;??????????????publishProgress(0);??????????????if?(mListener?!=?null)?{??????????????????mListener.onRefresh();??????????????}??????????????return?null;??????????}????????????@Override??????????protected?void?onProgressUpdate(Integer...?topMargin)?{??????????????updateHeaderView();??????????????headerLayoutParams.topMargin?=?topMargin[0];??????????????header.setLayoutParams(headerLayoutParams);??????????}????????}??????????????????class?HideHeaderTask?extends?AsyncTask<Void,?Integer,?Integer>?{????????????@Override??????????protected?Integer?doInBackground(Void...?params)?{??????????????int?topMargin?=?headerLayoutParams.topMargin;??????????????while?(true)?{??????????????????topMargin?=?topMargin?+?SCROLL_SPEED;??????????????????if?(topMargin?<=?hideHeaderHeight)?{??????????????????????topMargin?=?hideHeaderHeight;??????????????????????break;??????????????????}??????????????????publishProgress(topMargin);??????????????????sleep(10);??????????????}??????????????return?topMargin;??????????}????????????@Override??????????protected?void?onProgressUpdate(Integer...?topMargin)?{??????????????headerLayoutParams.topMargin?=?topMargin[0];??????????????header.setLayoutParams(headerLayoutParams);??????????}????????????@Override??????????protected?void?onPostExecute(Integer?topMargin)?{??????????????headerLayoutParams.topMargin?=?topMargin;??????????????header.setLayoutParams(headerLayoutParams);??????????????currentStatus?=?STATUS_REFRESH_FINISHED;??????????}??????}???????????????????private?void?sleep(int?time)?{??????????try?{??????????????Thread.sleep(time);??????????}?catch?(InterruptedException?e)?{??????????????e.printStackTrace();??????????}??????}??????????????????public?interface?PullToRefreshListener?{????????????????????????void?onRefresh();????????}????}?? 這 個類是整個下拉刷新功能中最重要的一個類,注釋已經(jīng)寫得比較詳細了,我再簡單解釋一下。首先在RefreshableView的構造函數(shù)中動態(tài)添加了剛剛 定義的pull_to_refresh這個布局作為下拉頭,然后在onLayout方法中將下拉頭向上偏移出了屏幕,再給ListView注冊了 touch事件。之后每當手指在ListView上滑動時,onTouch方法就會執(zhí)行。在onTouch方法中的第一行就調用了 setIsAbleToPull方法來判斷ListView是否滾動到了最頂部,只有滾動到了最頂部才會執(zhí)行后面的代碼,否則就視為正常的 ListView滾動,不做任何處理。當ListView滾動到了最頂部時,如果手指還在向下拖動,就會改變下拉頭的偏移值,讓下拉頭顯示出來,下拉的距 離設定為手指移動距離的1/2,這樣才會有拉力的感覺。如果下拉的距離足夠大,在松手的時候就會執(zhí)行刷新操作,如果距離不夠大,就僅僅重新隱藏下拉頭。
具體的刷新操作會在RefreshingTask中進行,其中在doInBackground方法中回調了PullToRefreshListener接口的onRefresh方法,這也是大家在使用RefreshableView時必須要去實現(xiàn)的一個接口,因為具體刷新的邏輯就應該寫在onRefresh方法中,后面會演示使用的方法。
另外每次在下拉的時候都還會調用updateHeaderView方法來改變下拉頭中的數(shù)據(jù),比如箭頭方向的旋轉,下拉文字描述的改變等。更加深入的理解請大家仔細去閱讀RefreshableView中的代碼。
現(xiàn)在我們已經(jīng)把下拉刷新的所有功能都完成了,接下來就要看一看如何在項目中引入下拉刷新了。打開或新建activity_main.xml作為程序主界面的布局,加入如下代碼:
[html] view plaincopy
<RelativeLayout?xmlns:android="http://schemas.android.com/apk/res/android"??????xmlns:tools="http://schemas.android.com/tools"??????android:layout_width="match_parent"??????android:layout_height="match_parent"??????tools:context=".MainActivity"?>????????<com.example.pulltorefreshtest.RefreshableView??????????android:id="@+id/refreshable_view"??????????android:layout_width="fill_parent"??????????android:layout_height="fill_parent"?>????????????<ListView??????????????android:id="@+id/list_view"??????????????android:layout_width="fill_parent"??????????????android:layout_height="fill_parent"?>??????????</ListView>??????</com.example.pulltorefreshtest.RefreshableView>????</RelativeLayout>?? 可以看到,我們在自定義的RefreshableView中加入了一個ListView,這就意味著給這個ListView加入了下拉刷新的功能,就是這么簡單!
然后我們再來看一下程序的主Activity,打開或新建MainActivity,加入如下代碼:
[java] view plaincopy
public?class?MainActivity?extends?Activity?{????????RefreshableView?refreshableView;??????ListView?listView;??????ArrayAdapter<String>?adapter;??????String[]?items?=?{?"A",?"B",?"C",?"D",?"E",?"F",?"G",?"H",?"I",?"J",?"K",?"L"?};????????@Override??????protected?void?onCreate(Bundle?savedInstanceState)?{??????????super.onCreate(savedInstanceState);??????????requestWindowFeature(Window.FEATURE_NO_TITLE);??????????setContentView(R.layout.activity_main);??????????refreshableView?=?(RefreshableView)?findViewById(R.id.refreshable_view);??????????listView?=?(ListView)?findViewById(R.id.list_view);??????????adapter?=?new?ArrayAdapter<String>(this,?android.R.layout.simple_list_item_1,?items);??????????listView.setAdapter(adapter);??????????refreshableView.setOnRefreshListener(new?PullToRefreshListener()?{??????????????@Override??????????????public?void?onRefresh()?{??????????????????try?{??????????????????????Thread.sleep(3000);??????????????????}?catch?(InterruptedException?e)?{??????????????????????e.printStackTrace();??????????????????}??????????????????refreshableView.finishRefreshing();??????????????}??????????},?0);??????}????}?? ?
可 以看到,我們通過調用RefreshableView的setOnRefreshListener方法注冊了一個監(jiān)聽器,當ListView正在刷新時就 會回調監(jiān)聽器的onRefresh方法,刷新的具體邏輯就在這里處理。而且這個方法已經(jīng)自動開啟了線程,可以直接在onRefresh方法中進行耗時操 作,比如向服務器請求最新數(shù)據(jù)等,在這里我就簡單讓線程睡眠3秒鐘。另外在onRefresh方法的最后,一定要調用RefreshableView中的 finishRefreshing方法,這個方法是用來通知RefreshableView刷新結束了,不然我們的ListView將一直處于正在刷新的 狀態(tài)。
不知道大家有沒有注意到,setOnRefreshListener這個方法其實是有兩個參數(shù)的,我們剛剛也是傳入了一個不起眼的0。那這 第二個參數(shù)是用來做什么的呢?由于RefreshableView比較智能,它會自動幫我們記錄上次刷新完成的時間,然后下拉的時候會在下拉頭中顯示距上 次刷新已過了多久。這是一個非常好用的功能,讓我們不用再自己手動去記錄和計算時間了,但是卻存在一個問題。如果當前我們的項目中有三個地方都使用到了下 拉刷新的功能,現(xiàn)在在一處進行了刷新,其它兩處的時間也都會跟著改變!因為刷新完成的時間是記錄在配置文件中的,由于在一處刷新更改了配置文件,導致在其 它兩處讀取到的配置文件時間已經(jīng)是更改過的了。那解決方案是什么?就是每個用到下拉刷新的地方,給setOnRefreshListener方法的第二個 參數(shù)中傳入不同的id就行了。這樣各處的上次刷新完成時間都是單獨記錄的,相互之間就不會再有影響。
好了,全部的代碼都在這里了,讓我們來運行一下,看看效果吧。
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?
效果看起來還是非常不錯的。我們最后再來總結一下,在項目中引入ListView下拉刷新功能只需三步:
1. 在Activity的布局文件中加入自定義的RefreshableView,并讓ListView包含在其中。
2. 在Activity中調用RefreshableView的setOnRefreshListener方法注冊回調接口。
3. 在onRefresh方法的最后,記得調用RefreshableView的finishRefreshing方法,通知刷新結束。
從此以后,在項目的任何地方,一分鐘引入下拉刷新功能妥妥的。
好了,今天的講解到此結束,有疑問的朋友請在下面留言。
源碼下載,請點擊這里
原文摘自:http://blog.csdn.net/guolin_blog/article/details/9255575
轉載于:https://www.cnblogs.com/YangBinChina/p/3555877.html
總結
以上是生活随笔為你收集整理的Android下拉刷新完全解析,教你如何一分钟实现下拉刷新功能 (转)的全部內容,希望文章能夠幫你解決所遇到的問題。
如果覺得生活随笔網(wǎng)站內容還不錯,歡迎將生活随笔推薦給好友。