1. 程式人生 > >Android ListView 實現下拉重新整理上拉載入

Android ListView 實現下拉重新整理上拉載入

1.簡介

       無疑,在Android開發中,ListView是使用非常頻繁的控制元件之一,ListView提供一個列表的容易,允許我們以列表的形式將資料展示到介面上,但是Google給我們提供的原生ListView的控制元件,雖然在功能上很強大,但是在使用者體驗和動態效果上,還是比較差勁的。為了改善使用者體驗,市面上紛紛出現了各種各樣的自定義的ListView,他們功能強大,介面美觀,使我們該需要學習的地方。其中,使用最頻繁的功能無疑就是ListView的下拉重新整理和上拉載入資料了,幾乎在沒一款內容型的App中都可以找到這種控制元件的身影,尤其是需要聯網獲取資料的模組,使用的就更為頻繁了,so,我們很有必要了解下這種效果是怎麼實現的。

2.開源元件PullToRefreshList介紹      

      既然Android和Java都是開源的,一些常見的功能或者效果就不難被找到。PullToRefresh就是一個典型的例子,PullToRefresh是老外寫的一個開源ListView元件,這個專案在ListView的基礎上擴充套件了ListView的功能,增強了Listview的使用者體驗,功能十分強大,而且很容易被整合到當前的專案中,你只需要呼叫簡單的API,即可省去很多不必要的麻煩,非常棒。以上是專案在Github的連結,有興趣的可以戳進去down下來,使用一下。這裡不是我們的重點,不想廢話了。

3.自定義ListView——下拉重新整理&上拉載入

     本部落格的重點講述一下自定義LisView,實現下拉重新整理和上拉載入的功能,實現類似於開源專案PullToRefresh的效果。好,既然如此,先看看我實現後的效果圖,再分析:      
        好,效果圖如上所示,下面逐步講解下實現的過程。首先,來觀察一下,ListView上方的佈局,我這裡稱其為“頭佈局”,這個所謂的頭佈局,大致功能是這樣的,一個ImageView顯示上下拉動方向的狀態的,ImageView相同的位置隱藏了一個ProgressBar,用來在資料重新整理時給個提示作用的。還有兩個TextView,上面用來顯示下拉重新整理時提醒使用者是如何操作的,例如“下拉重新整理”“鬆開重新整理”“正在重新整理”,另一個是用來顯示本次重新整理的時間的。比較簡單的佈局,下面是XML程式碼: [html]
view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:layout_width="match_parent"
  4.     android:layout_height="wrap_content"
  5.     android:orientation="horizontal">
  6.     <FrameLayout
  7.         android:layout_width="wrap_content"
  8.         android:layout_height="wrap_content"
  9.         android:layout_margin="10dip">
  10.         <ImageView
  11.             android:id="@+id/iv_listview_header_arrow"
  12.             android:layout_width="wrap_content"
  13.             android:layout_height="wrap_content"
  14.             android:layout_gravity="center"
  15.             android:minWidth="30dip"
  16.             android:src="@drawable/common_listview_headview_red_arrow"/>
  17.         <ProgressBar
  18.             android:id="@+id/pb_listview_header"
  19.             android:layout_width="wrap_content"
  20.             android:layout_height="wrap_content"
  21.             android:layout_gravity="center"
  22.             android:indeterminateDrawable="@drawable/common_progressbar"
  23.             android:visibility="gone"/>
  24.     </FrameLayout>
  25.     <LinearLayout
  26.         android:layout_width="fill_parent"
  27.         android:layout_height="wrap_content"
  28.         android:layout_gravity="center_vertical"
  29.         android:gravity="center_horizontal"
  30.         android:orientation="vertical">
  31.         <TextView
  32.             android:id="@+id/tv_listview_header_state"
  33.             android:layout_width="wrap_content"
  34.             android:layout_height="wrap_content"
  35.             android:text="下拉重新整理"
  36.             android:textColor="#FF0000"
  37.             android:textSize="18sp"/>
  38.         <TextView
  39.             android:id="@+id/tv_listview_header_last_update_time"
  40.             android:layout_width="wrap_content"
  41.             android:layout_height="wrap_content"
  42.             android:layout_marginTop="5dip"
  43.             android:text="最後重新整理時間: 2014-10-10 12:56:12"
  44.             android:textColor="@android:color/white"
  45.             android:textSize="14sp"/>
  46.     </LinearLayout>
  47. </LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal" >

    <FrameLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_margin="10dip" >

        <ImageView
            android:id="@+id/iv_listview_header_arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:minWidth="30dip"
            android:src="@drawable/common_listview_headview_red_arrow" />

        <ProgressBar
            android:id="@+id/pb_listview_header"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:indeterminateDrawable="@drawable/common_progressbar"
            android:visibility="gone" />
    </FrameLayout>

    <LinearLayout
        android:layout_width="fill_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:gravity="center_horizontal"
        android:orientation="vertical" >

        <TextView
            android:id="@+id/tv_listview_header_state"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="下拉重新整理"
            android:textColor="#FF0000"
            android:textSize="18sp" />

        <TextView
            android:id="@+id/tv_listview_header_last_update_time"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="5dip"
            android:text="最後重新整理時間: 2014-10-10 12:56:12"
            android:textColor="@android:color/white"
            android:textSize="14sp" />
    </LinearLayout>

</LinearLayout>
         下面講解一下ListView下方的佈局,我稱其為“腳佈局”,這個腳佈局就更簡單了,直接看XML程式碼好了: [html] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:layout_width="match_parent"
  4.     android:layout_height="wrap_content"
  5.     android:orientation="vertical">
  6.     <LinearLayout
  7.         android:layout_width="wrap_content"
  8.         android:layout_height="wrap_content"
  9.         android:layout_gravity="center_horizontal"
  10.         android:layout_margin="10dip"
  11.         android:gravity="center_vertical"
  12.         android:orientation="horizontal">
  13.         <ProgressBar
  14.             android:layout_width="wrap_content"
  15.             android:layout_height="wrap_content"
  16.             android:layout_gravity="center"
  17.             android:indeterminateDrawable="@drawable/common_progressbar"/>
  18.         <TextView
  19.             android:layout_width="wrap_content"
  20.             android:layout_height="wrap_content"
  21.             android:layout_marginLeft="10dip"
  22.             android:text="載入更多..."
  23.             android:textColor="#FF0000"
  24.             android:textSize="18sp"/>
  25.     </LinearLayout>
  26. </LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical" >

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_horizontal"
        android:layout_margin="10dip"
        android:gravity="center_vertical"
        android:orientation="horizontal" >

        <ProgressBar
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:indeterminateDrawable="@drawable/common_progressbar" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginLeft="10dip"
            android:text="載入更多..."
            android:textColor="#FF0000"
            android:textSize="18sp" />
    </LinearLayout>

</LinearLayout>
此外,兩個佈局都用到一個ProgressBar的背景,其XML如下: [html] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. <?xmlversion="1.0"encoding="utf-8"?>
  2. <rotatexmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:fromDegrees="0"
  4.     android:pivotX="50%"
  5.     android:pivotY="50%"
  6.     android:toDegrees="360">
  7.     <shape
  8.         android:innerRadiusRatio="3"
  9.         android:shape="ring"
  10.         android:useLevel="false">
  11.         <gradient
  12.             android:centerColor="#FF6666"
  13.             android:endColor="#FF0000"
  14.             android:startColor="#FFFFFF"
  15.             android:type="sweep"/>
  16.     </shape>
  17. </rotate>
<?xml version="1.0" encoding="utf-8"?>
<rotate xmlns:android="http://schemas.android.com/apk/res/android"
    android:fromDegrees="0"
    android:pivotX="50%"
    android:pivotY="50%"
    android:toDegrees="360" >

    <shape
        android:innerRadiusRatio="3"
        android:shape="ring"
        android:useLevel="false" >
        <gradient
            android:centerColor="#FF6666"
            android:endColor="#FF0000"
            android:startColor="#FFFFFF"
            android:type="sweep" />
    </shape>

</rotate>
        ListView的頭佈局和腳佈局已經做好了,那麼接下來該怎麼整合到ListView上去呢?首先我們來看看ListView的部分原始碼,很輕鬆能找到這兩個方法:addHeaderView(View v)addFooterView(View v),通過字面的意思就可以理解,這兩個方法分別是向ListView頂部新增一個View和向ListView的底部新增View的,有了這兩個方法,那麼上面的頭佈局和腳佈局就很容易被新增到ListView上了,並且成為ListView的一體。        其實,再看會發現,簡單的使用這兩個方法分別往ListView上新增頭佈局和腳佈局是不合理,新增上去的頭佈局和腳佈局會被顯示出來,並沒有被隱藏掉,為了實現下拉和上拉時能夠將頭佈局和腳佈局都“拉出來”並且還可以鬆開時,再次隱藏起來,我們可以使用View下的一個方法setPadding(int left, int top, int right, int bottom),這個方法設定View的Padding屬性,這裡,我們不必管left、right、bottom,只要設定top的值為頭佈局或者腳佈局的高度即可“隱藏”這兩個佈局,而且還可以在手指滑動螢幕的時候,動態的設定這個top的值,來實現頭佈局和腳佈局的顯示-隱藏-顯示。        還有一個非常重要的話題,就是這個top的值還設定為多少合適?上面說了,我們來頭佈局來說明一下,隱藏這個頭佈局需要將top值設定成top=- 頭佈局高度,那麼這個頭佈局的高度怎麼求得呢?很顯然,使用getHeight()是得不到頭佈局高度的,因為getHeight()方法是先控制元件在螢幕上展示完畢後得到的高度,顯然在這裡,這個ListView還在構建中,並沒有展示到螢幕上。所以注意了,我們先呼叫View下的measure(int widthMeasureSpec, int heightMeasureSpec)方法,將widthMeasureSpec和heightMeasureSpec分別設定為0,這裡的widthMeasureSpec和heightMeasureSpec並不是一個準備的值,而且指定一個規格或者標準讓系統幫我們測量View的寬高,當我們指定widthMeasureSpec和heightMeasureSpec分別為0的時候,系統將不採用這個規格去測量,而是根據實際情況去測量。之後,我們可以呼叫View下的getMeasuredHeight()方法獲取真實的頭佈局的高度,然後設定top = - 頭佈局實際高度,實現隱藏頭佈局。 [java] view plaincopyprint?在CODE上檢視程式碼片派生到我的程式碼片
  1. publicclass RefreshListView extends ListView implements OnScrollListener {  
  2.     privatestaticfinal String TAG = "RefreshListView";  
  3.     privateint firstVisibleItemPosition; // 螢幕顯示在第一個的item的索引
  4.     privateint downY; // 按下時y軸的偏移量
  5.     privateint headerViewHeight; // 頭佈局的高度
  6.     private View headerView; // 頭佈局的物件
  7.     privatefinalint DOWN_PULL_REFRESH = 0// 下拉重新整理狀態
  8.     privatefinalint RELEASE_REFRESH = 1// 鬆開重新整理
  9.     privatefinalint REFRESHING = 2// 正在重新整理中
  10.     privateint currentState = DOWN_PULL_REFRESH; // 頭佈局的狀態: 預設為下拉重新整理狀態
  11.     private Animation upAnimation; // 向上旋轉的動畫
  12.     private Animation downAnimation; // 向下旋轉的動畫
  13.     private ImageView ivArrow; // 頭佈局的剪頭
  14.     private ProgressBar mProgressBar; // 頭佈局的進度條
  15.     private TextView tvState; // 頭佈局的狀態
  16.     private TextView tvLastUpdateTime; // 頭佈局的最後更新時間
  17.     private OnRefreshListener mOnRefershListener;  
  18.     privateboolean isScrollToBottom; // 是否滑動到底部
  19.     private View footerView; // 腳佈局的物件
  20.     privateint footerViewHeight; // 腳佈局的高度
  21.     privateboolean isLoadingMore = false// 是否正在載入更多中
  22.     public RefreshListView(Context context, AttributeSet attrs) {  
  23.         super(context, attrs);  
  24.         initHeaderView();  
  25.         initFooterView();  
  26.         this.setOnScrollListener(this);  
  27.     }  
  28.     /** 
  29.      * 初始化腳佈局 
  30.      */
  31.     privatevoid initFooterView() {  
  32.         footerView = View.inflate(getContext(), R.layout.listview_footer, null);  
  33.         footerView.measure(00);  
  34.         footerViewHeight = footerView.getMeasuredHeight();  
  35.         footerView.setPadding(0, -footerViewHeight, 00);  
  36.         this.addFooterView(footerView);  
  37.     }  
  38.     /** 
  39.      * 初始化頭佈局 
  40.      */
  41.     privatevoid initHeaderView() {  
  42.         headerView = View.inflate(getContext(), R.layout.listview_header, null);  
  43.         ivArrow = (ImageView) headerView  
  44.                 .findViewById(R.id.iv_listview_header_arrow);  
  45.         mProgressBar = (ProgressBar) headerView  
  46.                 .findViewById(R.id.pb_listview_header);  
  47.         tvState = (TextView) headerView  
  48.                 .findViewById(R.id.tv_listview_header_state);  
  49.         tvLastUpdateTime = (TextView) headerView  
  50.                 .findViewById(R.id.tv_listview_header_last_update_time);  
  51.         // 設定最後重新整理時間
  52.         tvLastUpdateTime.setText("最後重新整理時間: " + getLastUpdateTime());  
  53.         headerView.measure(00); // 系統會幫我們測量出headerView的高度
  54.         headerViewHeight = headerView.getMeasuredHeight();  
  55.         headerView.setPadding(0, -headerViewHeight, 00);  
  56.         this.addHeaderView(headerView); // 向ListView的頂部新增一個view物件
  57.         initAnimation();  
  58.     }  
  59.     /** 
  60.      * 獲得系統的最新時間 
  61.      *  
  62.      * @return 
  63.      */
  64.     private String getLastUpdateTime() {  
  65.         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");  
  66.         return sdf.format(System.currentTimeMillis());  
  67.     }  
  68.     /** 
  69.      * 初始化動畫 
  70.      */
  71.     privatevoid initAnimation() {  
  72.         upAnimation = new RotateAnimation(0f, -180f,  
  73.                 Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,  
  74.                 0.5f);  
  75.         upAnimation.setDuration(500);  
  76.         upAnimation.setFillAfter(true); // 動畫結束後, 停留在結束的位置上
  77.         downAnimation = new RotateAnimation(-180f, -360f,  
  78.                 Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,  
  79.                 0.5f);  
  80.         downAnimation.setDuration(500);  
  81.         downAnimation.setFillAfter(true); // 動畫結束後, 停留在結束的位置上
  82.     }  
  83.     @Override
  84.     publicboolean onTouchEvent(MotionEvent ev) {  
  85.         switch (ev.getAction()) {  
  86.             case MotionEvent.ACTION_DOWN :  
  87.                 downY = (int) ev.getY();  
  88.                 break;  
  89.             case MotionEvent.ACTION_MOVE :  
  90.                 int moveY = (int) ev.getY();  
  91.                 // 移動中的y - 按下的y = 間距.
  92.                 int diff = (moveY - downY) / 2;  
  93.                 // -頭佈局的高度 + 間距 = paddingTop
  94.                 int paddingTop = -headerViewHeight + diff;  
  95.                 // 如果: -頭佈局的高度 > paddingTop的值 執行super.onTouchEvent(ev);
  96.                 if (firstVisibleItemPosition == 0
  97.                         && -headerViewHeight < paddingTop) {  
  98.                     if (paddingTop > 0 && currentState == DOWN_PULL_REFRESH) { // 完全顯示了.
  99.                         Log.i(TAG, "鬆開重新整理");  
  100.                         currentState = RELEASE_REFRESH;  
  101.                         refreshHeaderView();  
  102.                     } elseif (paddingTop < 0
  103.                             && currentState == RELEASE_REFRESH) { // 沒有顯示完全
  104.                         Log.i(TAG, "下拉重新整理");  
  105.                         currentState = DOWN_PULL_REFRESH;  
  106.                         refreshHeaderView();  
  107.                     }  
  108.                     // 下拉頭佈局
  109.                     headerView.setPadding(0, paddingTop, 00);  
  110.                     returntrue;  
  111.                 }  
  112.                 break;  
  113.             case MotionEvent.ACTION_UP :  
  114.                 // 判斷當前的狀態是鬆開重新整理還是下拉重新整理
  115.                 if (currentState == RELEASE_REFRESH) {  
  116.                     Log.i(TAG, "重新整理資料.");  
  117.                     // 把頭佈局設定為完全顯示狀態
  118.                     headerView.setPadding(0000);  
  119.                     // 進入到正在重新整理中狀態
  120.                     currentState = REFRESHING;  
  121.                     refreshHeaderView();  
  122.                     if (mOnRefershListener != null) {  
  123.                         mOnRefershListener.onDownPullRefresh(); // 呼叫使用者的監聽方法
  124.                     }  
  125.                 } elseif (currentState == DOWN_PULL_REFRESH) {  
  126.                     // 隱藏頭佈局
  127.                     headerView.setPadding(0, -headerViewHeight, 00);  
  128.                 }  
  129.                 break;  
  130.             default :  
  131.                 break;  
  132.         }  
  133.         returnsuper.onTouchEvent(ev);  
  134.     }  
  135.     /** 
  136.      * 根據currentState重新整理頭佈局的狀態 
  137.      */
  138.     privatevoid refreshHeaderView() {  
  139.         switch (currentState) {  
  140.             case DOWN_PULL_REFRESH : // 下拉重新整理狀態
  141.                 tvState.setText("下拉重新整理");  
  142.                 ivArrow.startAnimation(downAnimation); // 執行向下旋轉
  143.                 break;  
  144.             case RELEASE_REFRESH : // 鬆開重新整理狀態
  145.                 tvState.setText("鬆開重新整理");  
  146.                 ivArrow.startAnimation(upAnimation); // 執行向上旋轉
  147.                 break;  
  148.             case REFRESHING : // 正在重新整理中狀態
  149.                 ivArrow.clearAnimation();  
  150.                 ivArrow.setVisibility(View.GONE);  
  151.                 mProgressBar.setVisibility(View.VISIBLE);  
  152.                 tvState.setText("正在重新整理中...");  
  153.                 break;  
  154.             default :  
  155.                 break;  
  156.         }  
  157.     }  
  158.     /** 
  159.      * 當滾動狀態改變時回撥 
  160.      */
  161.     @Override
  162.     publicvoid onScrollStateChanged(AbsListView view, int scrollState) {  
  163.         if (scrollState == SCROLL_STATE_IDLE  
  164.                 || scrollState == SCROLL_STATE_FLING) {  
  165.             // 判斷當前是否已經到了底部
  166.             if (isScrollToBottom && !isLoadingMore) {  
  167.                 isLoadingMore = true;  
  168.                 // 當前到底部
  169.                 Log.i(TAG, "載入更多資料");  
  170.                 footerView.setPadding(0000);  
  171.                 this.setSelection(this.getCount());  
  172.                 if (mOnRefershListener != null) {  
  173.                     mOnRefershListener.onLoadingMore();  
  174.                 }  
  175.             }  
  176.         }  
  177.     }  
  178.     /** 
  179.      * 當滾動時呼叫 
  180.      *  
  181.      * @param firstVisibleItem 
  182.      *            當前螢幕顯示在頂部的item的position 
  183.      * @param visibleItemCount 
  184.      *            當前螢幕顯示了多少個條目的總數 
  185.      * @param totalItemCount 
  186.      *            ListView的總條目的總數 
  187.      */
  188.     @Override
  189.     publicvoid onScroll(AbsListView view, int firstVisibleItem,  
  190.             int visibleItemCount, int totalItemCount) {  
  191.         firstVisibleItemPosition = firstVisibleItem;  
  192.         if (getLastVisiblePosition() == (totalItemCount - 1)) {  
  193.             isScrollToBottom = true;  
  194.         } else {  
  195.             isScrollToBottom = false;  
  196.         }  
  197.     }  
  198.     /** 
  199.      * 設定重新整理監聽事件 
  200.      *  
  201.      * @param listener 
  202.      */
  203.     publicvoid setOnRefreshListener(OnRefreshListener listener) {  
  204.         mOnRefershListener = listener;  
  205.     }  
  206.     /** 
  207.      * 隱藏頭佈局 
  208.      */
  209.     publicvoid hideHeaderView() {  
  210.         headerView.setPadding(0, -headerViewHeight, 00);  
  211.         ivArrow.setVisibility(View.VISIBLE);  
  212.         mProgressBar.setVisibility(View.GONE);  
  213.         tvState.setText("下拉重新整理");  
  214.         tvLastUpdateTime.setText("最後重新整理時間: " + getLastUpdateTime());  
  215.         currentState = DOWN_PULL_REFRESH;  
  216.     }  
  217.     /** 
  218.      * 隱藏腳佈局 
  219.      */
  220.     publicvoid hideFooterView() {  
  221.         footerView.setPadding(0, -footerViewHeight, 00);  
  222.         isLoadingMore = false;  
  223.     }  
  224. }  
public class RefreshListView extends ListView implements OnScrollListener {

	private static final String TAG = "RefreshListView";
	private int firstVisibleItemPosition; // 螢幕顯示在第一個的item的索引
	private int downY; // 按下時y軸的偏移量
	private int headerViewHeight; // 頭佈局的高度
	private View headerView; // 頭佈局的物件

	private final int DOWN_PULL_REFRESH = 0; // 下拉重新整理狀態
	private final int RELEASE_REFRESH = 1; // 鬆開重新整理
	private final int REFRESHING = 2; // 正在重新整理中
	private int currentState = DOWN_PULL_REFRESH; // 頭佈局的狀態: 預設為下拉重新整理狀態

	private Animation upAnimation; // 向上旋轉的動畫
	private Animation downAnimation; // 向下旋轉的動畫

	private ImageView ivArrow; // 頭佈局的剪頭
	private ProgressBar mProgressBar; // 頭佈局的進度條
	private TextView tvState; // 頭佈局的狀態
	private TextView tvLastUpdateTime; // 頭佈局的最後更新時間

	private OnRefreshListener mOnRefershListener;
	private boolean isScrollToBottom; // 是否滑動到底部
	private View footerView; // 腳佈局的物件
	private int footerViewHeight; // 腳佈局的高度
	private boolean isLoadingMore = false; // 是否正在載入更多中

	public RefreshListView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initHeaderView();
		initFooterView();
		this.setOnScrollListener(this);
	}

	/**
	 * 初始化腳佈局
	 */
	private void initFooterView() {
		footerView = View.inflate(getContext(), R.layout.listview_footer, null);
		footerView.measure(0, 0);
		footerViewHeight = footerView.getMeasuredHeight();
		footerView.setPadding(0, -footerViewHeight, 0, 0);
		this.addFooterView(footerView);
	}

	/**
	 * 初始化頭佈局
	 */
	private void initHeaderView() {
		headerView = View.inflate(getContext(), R.layout.listview_header, null);
		ivArrow = (ImageView) headerView
				.findViewById(R.id.iv_listview_header_arrow);
		mProgressBar = (ProgressBar) headerView
				.findViewById(R.id.pb_listview_header);
		tvState = (TextView) headerView
				.findViewById(R.id.tv_listview_header_state);
		tvLastUpdateTime = (TextView) headerView
				.findViewById(R.id.tv_listview_header_last_update_time);

		// 設定最後重新整理時間
		tvLastUpdateTime.setText("最後重新整理時間: " + getLastUpdateTime());

		headerView.measure(0, 0); // 系統會幫我們測量出headerView的高度
		headerViewHeight = headerView.getMeasuredHeight();
		headerView.setPadding(0, -headerViewHeight, 0, 0);
		this.addHeaderView(headerView); // 向ListView的頂部新增一個view物件
		initAnimation();
	}

	/**
	 * 獲得系統的最新時間
	 * 
	 * @return
	 */
	private String getLastUpdateTime() {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
		return sdf.format(System.currentTimeMillis());
	}

	/**
	 * 初始化動畫
	 */
	private void initAnimation() {
		upAnimation = new RotateAnimation(0f, -180f,
				Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
				0.5f);
		upAnimation.setDuration(500);
		upAnimation.setFillAfter(true); // 動畫結束後, 停留在結束的位置上

		downAnimation = new RotateAnimation(-180f, -360f,
				Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF,
				0.5f);
		downAnimation.setDuration(500);
		downAnimation.setFillAfter(true); // 動畫結束後, 停留在結束的位置上
	}

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		switch (ev.getAction()) {
			case MotionEvent.ACTION_DOWN :
				downY = (int) ev.getY();
				break;
			case MotionEvent.ACTION_MOVE :
				int moveY = (int) ev.getY();
				// 移動中的y - 按下的y = 間距.
				int diff = (moveY - downY) / 2;
				// -頭佈局的高度 + 間距 = paddingTop
				int paddingTop = -headerViewHeight + diff;
				// 如果: -頭佈局的高度 > paddingTop的值 執行super.onTouchEvent(ev);
				if (firstVisibleItemPosition == 0
						&& -headerViewHeight < paddingTop) {
					if (paddingTop > 0 && currentState == DOWN_PULL_REFRESH) { // 完全顯示了.
						Log.i(TAG, "鬆開重新整理");
						currentState = RELEASE_REFRESH;
						refreshHeaderView();
					} else if (paddingTop < 0
							&& currentState == RELEASE_REFRESH) { // 沒有顯示完全
						Log.i(TAG, "下拉重新整理");
						currentState = DOWN_PULL_REFRESH;
						refreshHeaderView();
					}
					// 下拉頭佈局
					headerView.setPadding(0, paddingTop, 0, 0);
					return true;
				}
				break;
			case MotionEvent.ACTION_UP :
				// 判斷當前的狀態是鬆開重新整理還是下拉重新整理
				if (currentState == RELEASE_REFRESH) {
					Log.i(TAG, "重新整理資料.");
					// 把頭佈局設定為完全顯示狀態
					headerView.setPadding(0, 0, 0, 0);
					// 進入到正在重新整理中狀態
					currentState = REFRESHING;
					refreshHeaderView();

					if (mOnRefershListener != null) {
						mOnRefershListener.onDownPullRefresh(); // 呼叫使用者的監聽方法
					}
				} else if (currentState == DOWN_PULL_REFRESH) {
					// 隱藏頭佈局
					headerView.setPadding(0, -headerViewHeight, 0, 0);
				}
				break;
			default :
				break;
		}
		return super.onTouchEvent(ev);
	}

	/**
	 * 根據currentState重新整理頭佈局的狀態
	 */
	private void refreshHeaderView() {
		switch (currentState) {
			case DOWN_PULL_REFRESH : // 下拉重新整理狀態
				tvState.setText("下拉重新整理");
				ivArrow.startAnimation(downAnimation); // 執行向下旋轉
				break;
			case RELEASE_REFRESH : // 鬆開重新整理狀態
				tvState.setText("鬆開重新整理");
				ivArrow.startAnimation(upAnimation); // 執行向上旋轉
				break;
			case REFRESHING : // 正在重新整理中狀態
				ivArrow.clearAnimation();
				ivArrow.setVisibility(View.GONE);
				mProgressBar.setVisibility(View.VISIBLE);
				tvState.setText("正在重新整理中...");
				break;
			default :
				break;
		}
	}

	/**
	 * 當滾動狀態改變時回撥
	 */
	@Override
	public void onScrollStateChanged(AbsListView view, int scrollState) {

		if (scrollState == SCROLL_STATE_IDLE
				|| scrollState == SCROLL_STATE_FLING) {
			// 判斷當前是否已經到了底部
			if (isScrollToBottom && !isLoadingMore) {
				isLoadingMore = true;