Android-開發-PullToRefreshListView原理

1. 關於下拉刷新

下拉刷新這種用戶交互最早由twitter創始人洛倫•布里切特(Loren Brichter)發明;
有理論認爲,下拉刷新是一種適用於按照從新到舊的時間順序排列feeds的應用,在這種應用場景中看完舊的內容時,用戶會很自然地下拉查找更新的內容,因此下拉刷新就顯得非常合理

2. 實現原理

上面這些例子,外觀做得再好看,他的本質上都一樣,那就是一個下拉刷新控件通常由以下幾部分組成:
【1】Header
Header通常有下拉箭頭,文字,進度條等元素,根據下拉的距離來改變它的狀態,從而顯示不同的樣式
【2】Content
這部分是內容區域,網上有很多例子都是直接在ListView裏面添加Header,但這就有侷限性,因爲好多情況下並不一定是用ListView來顯示數據。我們把要顯示內容的View放置在我們的一個容器中,如果你想實現一個用ListView顯示數據的下拉刷新,你需要創建一個ListView旋轉到我的容器中。我們處理這個容器的事件(down, move, up),如果向下拉,則把整個佈局向下滑動,從而把header顯示出來。
【3】Footer
Footer可以用來顯示向上拉的箭頭,自動加載更多的進度條等



以上三部分總結的說來,就是如下圖所示的這種佈局結構:



關於上圖,需要說明幾點:
1、這個佈局擴展於LinearLayout,垂直排列
2、從上到下的順序是:Header, Content, Footer
3、Content填充滿父控件,通過設置top, bottom的padding來使Header和Footer不可見,也就是讓它超出屏幕外
4、下拉時,調用scrollTo方法來將整個佈局向下滑動,從而把Header顯示出來,上拉正好與下拉相反。
5、派生類需要實現的是:將Content View填充到父容器中,比如,如果你要使用的話,那麼你需要把ListView, ScrollView, WebView等添加到容器中。
6、上圖中的紅色區域就是屏的大小(嚴格來說,這裏說屏幕大小並不準確,應該說成內容區域更加準確)


3. 具體實現

明白了實現原理與過程,我們嘗試來具體實現,首先,爲了以後更好地擴展,設計更加合理,我們把下拉刷新的功能抽象成一個接口:

1、IPullToRefresh<T extends View>

它具體的定義方法如下:
  1. public interface IPullToRefresh<T extends View> {  
  2.     public void setPullRefreshEnabled(boolean pullRefreshEnabled);  
  3.     public void setPullLoadEnabled(boolean pullLoadEnabled);  
  4.     public void setScrollLoadEnabled(boolean scrollLoadEnabled);  
  5.     public boolean isPullRefreshEnabled();  
  6.     public boolean isPullLoadEnabled();  
  7.     public boolean isScrollLoadEnabled();  
  8.     public void setOnRefreshListener(OnRefreshListener<T> refreshListener);  
  9.     public void onPullDownRefreshComplete();  
  10.     public void onPullUpRefreshComplete();  
  11.     public T getRefreshableView();  
  12.     public LoadingLayout getHeaderLoadingLayout();  
  13.     public LoadingLayout getFooterLoadingLayout();  
  14.     public void setLastUpdatedLabel(CharSequence label);  
  15. }  
這個接口是一個泛型的,它接受View的派生類,因爲要放到我們的容器中的不就是一個View嗎?

2、PullToRefreshBase<T extends View>
這個類實現了IPullToRefresh接口,它是從LinearLayout繼承過來,作爲下拉刷新的一個抽象基類,如果你想實現ListView的下拉刷新,只需要擴展這個類,實現一些必要的方法就可以了。這個類的職責主要有以下幾點:
  • 處理onInterceptTouchEvent()和onTouchEvent()中的事件當內容的View(比如ListView)正如處於最頂部,此時再向下拉,我們必須截斷事件,然後move事件就會把後續的事件傳遞到onTouchEvent()方法中,然後再在這個方法中,我們根據move的距離再進行scroll整個View。
  • 負責創建Header、Footer和Content View在構造方法中調用方法去創建這三個部分的View,派生類可以重寫這些方法,以提供不同式樣的Header和Footer,它會調用createHeaderLoadingLayout和createFooterLoadingLayout方法來創建Header和Footer創建Content View的方法是一個抽象方法,必須讓派生類來實現,返回一個非null的View,然後容器再把這個View添加到自己裏面。
  • 設置各種狀態:這裏面有很多狀態,如下拉、上拉、刷新、加載中、釋放等,它會根據用戶拉動的距離來更改狀態,狀態的改變,它也會把Header和Footer的狀態改變,然後Header和Footer會根據狀態去顯示相應的界面式樣。
3、PullToRefreshBase<T extends View>繼承關係
這裏我實現了三個下拉刷新的派生類,分別是ListView、ScrollView、WebView三個,它們的繼承關係如下:


圖四、PullToRefreshBase類的繼承關係

關於PullToRefreshBase類及其派和類,有幾點需要說明:
  • 對於ListView,ScrollView,WebView這三種情況,他們是否滑動到最頂部或是最底部的實現是不一樣的,所以,在PullToRefreshBase類中需要調用兩個抽象方法來判斷當前的位置是否在頂部或底部,而其派生類必須要實現這兩個方法。比如對於ListView,它滑動到最頂部的條件就是第一個child完全可見並且first postion是0。這兩個抽象方法是:
  1. /** 
  2.  * 判斷刷新的View是否滑動到頂部 
  3.  *  
  4.  * @return true表示已經滑動到頂部,否則false 
  5.  */  
  6. protected abstract boolean isReadyForPullDown();  
  7.   
  8. /** 
  9.  * 判斷刷新的View是否滑動到底 
  10.  *  
  11.  * @return true表示已經滑動到底部,否則false 
  12.  */  
  13. protected abstract boolean isReadyForPullUp();  
  • 創建可下拉刷新的View(也就是content view)的抽象方法是

  1. /** 
  2.  * 創建可以刷新的View 
  3.  *  
  4.  * @param context context 
  5.  * @param attrs 屬性 
  6.  * @return View 
  7.  */  
  8. protected abstract T createRefreshableView(Context context, AttributeSet attrs);  
4、LoadingLayout
LoadingLayout是刷新Layout的一個抽象,它是一個抽象基類。Header和Footer都擴展於這個類。這類抽象類,提供了兩個抽象方法:
  • getContentSize
這個方法返回當前這個刷新Layout的大小,通常返回的是佈局的高度,爲了以後可以擴展爲水平拉動,所以方法名字沒有取成getLayoutHeight()之類的,這個返回值,將會作爲鬆手後是否可以刷新的臨界值,如果下拉的偏移值大於這個值,就認爲可以刷新,否則不刷新,這個方法必須由派生類來實現。
  • setState
這個方法用來設置當前刷新Layout的狀態,PullToRefreshBase類會調用這個方法,當進入下拉,鬆手等動作時,都會調用這個方法,派生類裏面只需要根據這些狀態實現不同的界面顯示,如下拉狀態時,就顯示出箭頭,刷新狀態時,就顯示loading的圖標。
可能的狀態值有:RESET, PULL_TO_REFRESH, RELEASE_TO_REFRESH, REFRESHING, NO_MORE_DATA

LoadingLayout及其派生類的繼承關係如下圖所示:


圖五、LoadingLayout及其派生類的類圖

我們可以隨意地制定自己的Header和Footer,我們也可以實現如圖一和圖二中顯示的各種下拉刷新案例中的Header和Footer,只要重寫上述兩個方法getContentSize()和setState()就行了。HeaderLoadingLayout,它默認是顯示箭頭式樣的佈局,而RotateLoadingLayout則是顯示一個旋轉圖標的式樣。

5、事件處理
我們必須重寫PullToRefreshBase類的兩個事件相關的方法onInterceptTouchEvent()和onTouchEvent()方法。由於ListView,ScrollView,WebView它們是放到PullToRefreshBase內部的,所在事件先是傳遞到PullToRefreshBase#onInterceptTouchEvent()方法中,所以我們應該在這個方法中去處理ACTION_MOVE事件,判斷如果當前ListView,ScrollView,WebView是否在最頂部或最底部,如果是,則開始截斷事件,一旦事件被截斷,後續的事件就會傳遞到PullToRefreshBase#onInterceptTouchEvent()方法中,我們再在ACTION_MOVE事件中去移動整個佈局,從而實現下拉或上拉動作。

6、滾動佈局(scrollTo)
如圖三的佈局結構可知,默認情況下Header和Footer是放置在Content View的最上面和最下面,通過設置padding來讓他跑到屏幕外面去了,如果我們將整個佈局向下滾動(scrollTo)一定距離,那麼Header就會被顯示出來,基於這種情況,所以在我的實現中,最終我是調用scrollTo來實現下拉動作的。

總的說來,實現的重要的點就這些,具體的一些細節在實現在會碰到很多,可以參考代碼。

4. 如何使用

使用下拉刷新的代碼如下
 
  1. @Override  
  2.     public void onCreate(Bundle savedInstanceState) {  
  3.         super.onCreate(savedInstanceState);  
  4.           
  5.         mPullListView = new PullToRefreshListView(this);  
  6.         setContentView(mPullListView);  
  7.           
  8.         // 上拉加載不可用  
  9.         mPullListView.setPullLoadEnabled(false);  
  10.         // 滾動到底自動加載可用  
  11.         mPullListView.setScrollLoadEnabled(true);  
  12.           
  13.         mCurIndex = mLoadDataCount;  
  14.         mListItems = new LinkedList<String>();  
  15.         mListItems.addAll(Arrays.asList(mStrings).subList(0, mCurIndex));  
  16.         mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mListItems);  
  17.           
  18.         // 得到實際的ListView  
  19.         mListView = mPullListView.getRefreshableView();  
  20.         // 綁定數據  
  21.         mListView.setAdapter(mAdapter);         
  22.         // 設置下拉刷新的listener  
  23.         mPullListView.setOnRefreshListener(new OnRefreshListener<ListView>() {  
  24.             @Override  
  25.             public void onPullDownToRefresh(PullToRefreshBase<ListView> refreshView) {  
  26.                 mIsStart = true;  
  27.                 new GetDataTask().execute();  
  28.             }  
  29.   
  30.             @Override  
  31.             public void onPullUpToRefresh(PullToRefreshBase<ListView> refreshView) {  
  32.                 mIsStart = false;  
  33.                 new GetDataTask().execute();  
  34.             }  
  35.         });  
  36.         setLastUpdateTime();  
  37.           
  38.         // 自動刷新  
  39.         mPullListView.doPullRefreshing(true500);  
  40.     }  
這是初始化一個下拉刷新的佈局,並且調用setContentView來設置到Activity中。
在下拉刷新完成後,我們可以調用onPullDownRefreshComplete()和onPullUpRefreshComplete()方法來停止刷新和加載

5. 運行效果

參考DEMO:http://download.csdn.net/detail/q908555281/9135707





發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章