1. 程式人生 > >再也不用擔心下拉重新整理,上拉載入啦!-自定義ListView對上拉重新整理,上拉載入的詳解

再也不用擔心下拉重新整理,上拉載入啦!-自定義ListView對上拉重新整理,上拉載入的詳解


前言:

      看過許多下拉重新整理的例子,好多大牛們的程式碼寫的很完美,讓人羨慕嫉妒恨~~~,可是,對於下拉重新整理時的手勢操作卻沒有給出詳細的解釋,當一堆堆邏輯程式碼出來的時候,對於我們這些菜鳥來說,理解起來真是讓人腦子都大了。為了解放大腦(懶得自己進行全面分析),一步一步詳解下拉操作,媽媽再也不用擔心ListView 下拉重新整理是什麼鬼啦!~~

先上效果圖:~~

 上拉載入資料:            下拉重新整理:下拉距離短不重新整理資料      下拉重新整理資料:

思路詳解:

     自定義的帶有下拉重新整理和上拉載入的ListView開始時,跟系統的ListView一樣。不過多了個header和footer只不過這兩個佈局以不同的方式隱藏起來了而已。(header是在手機螢幕外的上面,footer是直接隱藏起來了。因為下拉和上拉載入不同,下拉載入有手勢判斷,要出現動畫效果,根據下拉的各種手勢,來設定具體的操作。如果直接跟footer一樣首先預設header的View.setVisibility(GONE),當有下拉手勢時再設定View.setVisibility(View.VISIBLE)就會沒有良好的動畫效果。

)如下圖所示。

PS:破電腦只有自帶的Window畫圖工具。湊合看吧

先將程式碼拆分~ 上拉載入跟下拉重新整理分開來講,後面會有完整的程式碼。~~~

上拉載入資料:

     由於這個比下拉重新整理簡單。先理解這個。上拉載入適用於需要載入的資料量很大時,如果一下子載入完。會使ListView出現卡頓。這時候,如果利用上拉載入。先載入一部分資料。當上拉時,再載入其他的一部分資料。這樣就會有很好的使用者體驗。

   footer佈局檔案

<?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="match_parent"
    android:layout_centerInParent="true"
    android:orientation="vertical">
    
    <LinearLayout
        android:id="@+id/ll_footer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"

        android:orientation="vertical">
        <ProgressBar
            android:id="@+id/footer_pb"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="?android:attr/progressBarStyleSmall"
            android:layout_gravity="center"
            />
        <TextView
            android:id="@+id/footer_tv"
            android:text="footer正在載入。。"
            android:textSize="16sp"

            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        
    </LinearLayout>

</LinearLayout>


接下來看上拉載入邏輯:上拉載入我們利用的是AbsListView.OnScrollListener這個介面。 它有兩個方法需要重寫:

1.publicvoid onScroll(AbsListView view,int firstVisibleItem,int visibleItemCount,int totalItemCount) {};

2.publicvoid onScrollStateChanged(AbsListView view,int scrollState) {};

借鑑別的地方的對這兩個方法的詳細解釋。他講解的很詳細啦:

 new OnScrollListener() {    
        boolean isLastRow = false;    
        
        @Override    
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {    
            //滾動時一直回撥,直到停止滾動時才停止回撥。單擊時回撥一次。    
            //firstVisibleItem:當前能看見的第一個列表項ID(從0開始)    
            //visibleItemCount:當前能看見的列表項個數(小半個也算)    
            //totalItemCount:列表項共數    
        
            //判斷是否滾到最後一行    
            if (firstVisibleItem + visibleItemCount == totalItemCount && totalItemCount > 0) {    
                isLastRow = true;    
            }    
        }    
        @Override    
        public void onScrollStateChanged(AbsListView view, int scrollState) {    
            //正在滾動時回撥,回撥2-3次,手指沒拋則回撥2次。scrollState = 2的這次不回撥    
            //回撥順序如下    
            //第1次:scrollState = SCROLL_STATE_TOUCH_SCROLL(1) 正在滾動    
            //第2次:scrollState = SCROLL_STATE_FLING(2) 手指做了拋的動作(手指離開螢幕前,用力滑了一下)    
            //第3次:scrollState = SCROLL_STATE_IDLE(0) 停止滾動              

            //當螢幕停止滾動時為0;當螢幕滾動且使用者使用的觸碰或手指還在螢幕上時為1;  
            //由於使用者的操作,螢幕產生慣性滑動時為2  
        
            //當滾到最後一行且停止滾動時,執行載入    
            if (isLastRow && scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {    
                //載入元素    
                ......    
        
                isLastRow = false;    
            }    
        }    
    }  

好了,瞭解了OnScrollListener的這兩個方法具體是幹什麼的,接下來看我們怎麼實現~~~:

 上拉載入的關鍵就在這個介面中實現:

首先是LoadListView中的定義的變數:

private int lastVisibleItem; //最後一個可見項
    private int totalItems; //總的item
    private View footer; //底部View+頭部View;
    private boolean isLoading = false;//是否正在載入
    private ILoadListener iListener;//自定義的一個載入介面。暴露給MainActivity讓它實現具體載入操作。可以根據需求不同而改寫。


 載入佈局檔案以及設定監聽:

private void initViews(Context context) {
        //獲得footer+header佈局檔案
        LayoutInflater inflater =LayoutInflater.from(context);
        footer = inflater.inflate(R.layout.footer,null);

        footer.findViewById(R.id.ll_footer).setVisibility(GONE);//初始化時設定footer不可見
        this.addFooterView(footer);
        this.setOnScrollListener(this);//設定滾動監聽


    }

重寫OnScrollListener的這兩個方法:

@Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
               if(lastVisibleItem==totalItems&&scrollState ==SCROLL_STATE_IDLE){
            //如果不是在載入
           if(!isLoading){
                footer.findViewById(R.id.ll_footer).setVisibility(View.VISIBLE);
               iListener.onLoad();
               isLoading =true;

           }
        }


    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        this.lastVisibleItem=firstVisibleItem+visibleItemCount;
        this.totalItems = totalItemCount;
  
    }

這樣就差不多了。接下來再完成介面的設定,具體操作再MainActivity中實現。

/**
     * 載入更多資料的回撥介面
     */
    public interface ILoadListener {
        public void onLoad();
    }
    //上拉載入完畢
    public void loadCompleted(){
        isLoading =false;
        footer.findViewById(R.id.ll_footer).setVisibility(GONE);
    }


    public void setInterface(ILoadListener iListener){
        
        this.iListener=iListener;
    }
上拉載入就完成了80%了,具體操作時,在MainActivity中:
    private LoadListView mListView;
    private List<String> datas;
    private ArrayAdapter<String> arrayAdapter;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setupViews();
        initDatas();
    }

    private void initDatas() {
        for (int i = 0; i < 16; i++) {
            datas.add("ListView的資料"+i+"");
        }
    }
    private void initNewDatas(){
        for (int i = 0; i < 3; i++) {
            datas.add("footer加載出的資料"+i+"");
        }

    }


 private void setupViews() {

        mListView = (LoadListView) findViewById(R.id.lv_main);
        //上拉載入介面
        mListView.setInterface( this);
        datas = new ArrayList<String>();
        arrayAdapter = new ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_expandable_list_item_1,datas);
        mListView.setAdapter(arrayAdapter);



    }
    
實現LoadList暴露的介面中的onLoad()方法:
   //實現onLoad()方法。
    @Override
    public void onLoad() {
        //新增延時效果模擬資料載入
        Handler handler= new Handler() ;
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                initNewDatas();//得到新資料
                arrayAdapter.notifyDataSetChanged();//重新整理ListView;
                mListView.loadCompleted();
            }
        }, 2000);
    }
OK~上拉載入資料就大功告成了~~
下拉重新整理:

 首先是header佈局檔案:

<?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">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="10dip"
        android:paddingBottom="10dip"
        >
        <LinearLayout
            android:id="@+id/ll_header"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:orientation="vertical">
            <TextView
                android:id="@+id/tip"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="下拉可以重新整理"/>
            <TextView
                android:id="@+id/tv_lastupdate_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                />




        </LinearLayout>

        <ImageView
            android:id="@+id/arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@id/ll_header"
            android:layout_marginRight="20dip"
            android:src="@drawable/pull_down_refresh_arrow"

            />
        <ProgressBar
            android:id="@+id/header_pb"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="?android:attr/progressBarStyleSmall"
            android:layout_toLeftOf="@id/ll_header"
            android:layout_marginRight="20dip"
            android:visibility="gone"/>


    </RelativeLayout>


</LinearLayout>

LoadList中自定義的一些變數:

private boolean isRemark = false;//判斷是否在當前頁的最頂端並下滑
    private int startY; //Y座標 記錄手指開始按下的座標
    private RLoadListener rLoadListener;//自定義的一個載入介面。暴露給MainActivity讓它實現具體載入操作。可以根據需求不同而改寫。

    private int scrollState;//當前滾動的 狀態

    private int headerHeight;//頂部佈局檔案的高度

    final int NONE= 0;//正常狀態
    final int PULL =1;//下拉
    final int RELESE =2;//釋放
    final int REFLASHING =3; //重新整理

    private int state=0;//判斷當前狀態,預設為正常狀態


    private int firstVisibleItem;//第一個可見項
private View header; //頭部View;


新增頭部提示到ListView中:

 LayoutInflater inflater =LayoutInflater.from(context);
    

    

        header = inflater.inflate(R.layout.header,null);
        //測量header的寬和高
        measureView(header);
        //記錄下header的高
        headerHeight = header.getMeasuredHeight();
        topPadding(-headerHeight);
        this.addHeaderView(header);   
        this.setOnScrollListener(this);//設定滾動監聽

這裡新增頭佈局的時候需要計算header到底要移出螢幕多少的距離(移出的距離即為header的高),並且要告知父佈局:

/**
     * 通知父佈局,佔用的寬和高
     * @param view
     */
    private void measureView(View view) {

        //得到view的佈局寬高
        ViewGroup.LayoutParams vlp = view.getLayoutParams();
        //如果沒有就new一個
        if (vlp==null){
            vlp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        }
        //ViewGroup.getChildMeasureSpec(int spec,int padding,int childDimension) 1.父view的詳細尺寸 2.view當前尺寸的下的邊距 3.child在當前尺寸下的寬(高)
        int width = ViewGroup.getChildMeasureSpec(0,0,vlp.width);
        int height;
        int tempHeight = vlp.height;
        if (tempHeight>0){
            height = MeasureSpec.makeMeasureSpec(tempHeight,MeasureSpec.EXACTLY);

        }else {
            height =MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
        }
        view.measure(width, height);

    }


    /**
     * 設定header佈局的上邊距
     * @param topPadding
     */
    private void topPadding(int topPadding) {
        header.setPadding(header.getPaddingLeft(), topPadding, header.getPaddingRight(), header.getPaddingBottom());
        //重繪
        header.invalidate();

    }


在OnScrollListener的兩個方法中記錄一些狀態量便於後面對OnTouch事件的操作:

@Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        this.scrollState =scrollState;
       
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        
        this.firstVisibleItem = firstVisibleItem;
    }

利用OnTouchEvent對下拉手勢操作進行監聽:

先來說明下拉重新整理會出現的情況:1.下拉距離過短,不進行資料重新整理。2.下拉到一定距離重新整理資料。

當進行下拉重新整理的時候,手勢有3種狀態:1.剛按下的時候;2.手指下滑移動的時候;3.手指擡起釋放的時候。這3種手指狀態又對應了header不同的View狀態,然後根據view的狀態,再來改變header的顯示。

就是靠下面這三個方法來實現下拉的核心操作分析在程式碼後面:

  @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN://如果按下
                if (firstVisibleItem==0){
                    isRemark=true;
                    startY = (int) ev.getY();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                onMove(ev);
                break;
            case MotionEvent.ACTION_UP:
                if (state==RELESE){
                    state = REFLASHING;
                    //載入最新資料
                    reflashViewByState();
                    rLoadListener.onRefresh();
                }else if (state==PULL){
                    state = NONE;
                    isRemark = false;
                    reflashViewByState();
                }
                break;


        }

        return super.onTouchEvent(ev);
    }

    /**
     * 判斷移動的過程
     *
     * @param ev
     */
    private void onMove(MotionEvent ev) {
        if (!isRemark){
            return;
        }
        int tempY = (int) ev.getY();
        //記錄滑動距離
        int space = tempY-startY;
        //比較滑動距離和header的高
        int topPadding = space-headerHeight;
        switch (state){
            case NONE:
                if (space>0){
                    state = PULL;
                    reflashViewByState();
                }
                break;
            case PULL:
                //重繪header
                topPadding(topPadding);
                if (space>headerHeight+30&&scrollState==SCROLL_STATE_TOUCH_SCROLL){
                    state =RELESE;
                    reflashViewByState();

                }
                break;
            case RELESE:
                topPadding(topPadding);
                if (space<headerHeight+30){
                    state =PULL;
                    reflashViewByState();
                }else if(space<=0){//如果space<0說明向上滑。所以firstVisibleItem就不是0了 所以將isRemark設定為false;即listView現在不是最頂端的位置
                    state = NONE;
                    isRemark = false;
                    reflashViewByState();
                }
                break;

        }

    }



    /**
     * 根據當前狀態,改變介面顯示
     */
    private void reflashViewByState() {
        TextView tip = (TextView) header.findViewById(R.id.tip);
        ImageView arrow = (ImageView) header.findViewById(R.id.arrow);
        ProgressBar header_pb = (ProgressBar) header.findViewById(R.id.header_pb);

        RotateAnimation animation = new RotateAnimation(0,180,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
        animation.setDuration(500);
        animation.setFillAfter(true);

        RotateAnimation animation2 = new RotateAnimation(180,0,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
        animation2.setDuration(500);
        animation2.setFillAfter(true);

        /**
         * 四種狀態動畫的改變
         */
        switch (state){
            case NONE:
                arrow.clearAnimation();
                topPadding(-headerHeight);
                break;
            case PULL:
                arrow.setVisibility(View.VISIBLE);
                header_pb.setVisibility(View.GONE);
                tip.setText("下拉可以重新整理!!");
                arrow.clearAnimation();
                arrow.setAnimation(animation2);
                break;
            case RELESE:
                arrow.setVisibility(View.VISIBLE);
                header_pb.setVisibility(View.GONE);
                tip.setText("鬆開可以重新整理!!");
                arrow.clearAnimation();
                arrow.setAnimation(animation);
                break;
            case REFLASHING:
                topPadding(50);
                arrow.setVisibility(View.GONE);
                header_pb.setVisibility(View.VISIBLE);
                tip.setText("正在重新整理...");
                arrow.clearAnimation();
                break;
        }



    }

手指操作的圖示:


由於圖太大,放到裡面看不清。。。需要高清無碼大圖的點選這裡:點我!!點我!!點我!!

這樣,下拉重新整理的核心程式碼已經完成了,接下來就是設定介面,和重新整理完所做的動作的方法:

    下拉重新整理介面,暴露給MainActivity來具體實現到底刷新出什麼資料:

 /**
     * 下拉重新整理介面
     */
    public interface RLoadListener{
        public void onRefresh();

    }

    public void setReflashInterface(RLoadListener rLoadListener){
        this.rLoadListener =rLoadListener;

    }

重新整理完要做的工作:

    /**
     * 獲取完整資料
     *
     */
    public void reflashComplete(){
        state = NONE;
        isRemark = false;
        reflashViewByState();
        TextView lasetupdate_time = (TextView) header.findViewById(R.id.tv_lastupdate_time);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
        Date date = new Date(System.currentTimeMillis());
        String time = simpleDateFormat.format(date);
        lasetupdate_time.setText(time);



    }

duang~duang~duang ~ 接下來就看怎麼用這些東西在MainActivity中。

貼上完整的程式碼~~。

1. 佈局檔案:

    footer.xml

<?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="match_parent"
    android:layout_centerInParent="true"
    android:orientation="vertical">
    
    <LinearLayout
        android:id="@+id/ll_footer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"

        android:orientation="vertical">
        <ProgressBar
            android:id="@+id/footer_pb"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="?android:attr/progressBarStyleSmall"
            android:layout_gravity="center"
            />
        <TextView
            android:id="@+id/footer_tv"
            android:text="footer正在載入。。"
            android:textSize="16sp"

            android:gravity="center"
            android:layout_width="match_parent"
            android:layout_height="wrap_content" />
        
    </LinearLayout>

</LinearLayout>

header.xml:

<?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">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="10dip"
        android:paddingBottom="10dip"
        >
        <LinearLayout
            android:id="@+id/ll_header"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:orientation="vertical">
            <TextView
                android:id="@+id/tip"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="下拉可以重新整理"/>
            <TextView
                android:id="@+id/tv_lastupdate_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                />




        </LinearLayout>

        <ImageView
            android:id="@+id/arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@id/ll_header"
            android:layout_marginRight="20dip"
            android:src="@drawable/pull_down_refresh_arrow"

            />
        <ProgressBar
            android:id="@+id/header_pb"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="?android:attr/progressBarStyleSmall"
            android:layout_toLeftOf="@id/ll_header"
            android:layout_marginRight="20dip"
            android:visibility="gone"/>


    </RelativeLayout>


</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">
    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingTop="10dip"
        android:paddingBottom="10dip"
        >
        <LinearLayout
            android:id="@+id/ll_header"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:orientation="vertical">
            <TextView
                android:id="@+id/tip"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:text="下拉可以重新整理"/>
            <TextView
                android:id="@+id/tv_lastupdate_time"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                />




        </LinearLayout>

        <ImageView
            android:id="@+id/arrow"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@id/ll_header"
            android:layout_marginRight="20dip"
            android:src="@drawable/pull_down_refresh_arrow"

            />
        <ProgressBar
            android:id="@+id/header_pb"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            style="?android:attr/progressBarStyleSmall"
            android:layout_toLeftOf="@id/ll_header"
            android:layout_marginRight="20dip"
            android:visibility="gone"/>


    </RelativeLayout>


</LinearLayout>

activity_main.xml

<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" android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<com.example.administrator.listviewtest.LoadListView
    android:id="@+id/lv_main"
    android:layout_width="match_parent"
    android:layout_height="match_parent"></com.example.administrator.listviewtest.LoadListView>
</RelativeLayout>

LoadListView.java

package com.example.administrator.listviewtest;

import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * Created by Administrator on 2015-10-12.
 */
public class LoadListView extends ListView implements AbsListView.OnScrollListener {
    private int lastVisibleItem; //最後一個可見項
    private int totalItems; //總的item
    private View footer,header; //底部View+頭部View;
    private boolean isLoading = false;//是否正在載入
    private ILoadListener iListener;//自定義的一個載入介面。暴露給MainActivity讓它實現具體載入操作。可以根據需求不同而改寫。

    private boolean isRemark = false;//判斷是否在當前頁的最頂端並下滑
    private int startY; //Y座標 記錄手指開始按下的座標
    private RLoadListener rLoadListener;//自定義的一個載入介面。暴露給MainActivity讓它實現具體載入操作。可以根據需求不同而改寫。

    private int scrollState;//當前滾動的 狀態

    private int headerHeight;//頂部佈局檔案的高度

    final int NONE= 0;//正常狀態
    final int PULL =1;//下拉
    final int RELESE =2;//釋放
    final int REFLASHING =3; //重新整理

    private int state=0;//判斷當前狀態,預設為正常狀態


    private int firstVisibleItem;//第一個可見項



    public LoadListView(Context context) {
        this(context,null);

    }

    public LoadListView(Context context, AttributeSet attrs) {
        this(context, attrs, android.R.attr.listViewStyle);

    }

    public LoadListView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);

        initViews(context);


    }

    /**
     * 新增底部+頭部提示到ListVIew
     * @param context
     */
    private void initViews(Context context) {
        //獲得footer+header佈局檔案
        LayoutInflater inflater =LayoutInflater.from(context);
        footer = inflater.inflate(R.layout.footer,null);

        footer.findViewById(R.id.ll_footer).setVisibility(GONE);//初始化時設定footer不可見

        header = inflater.inflate(R.layout.header,null);
        //測量header的寬和高
        measureView(header);
        //記錄下header的高
        headerHeight = header.getMeasuredHeight();
        topPadding(-headerHeight);

        this.addHeaderView(header);
        this.addFooterView(footer);
        this.setOnScrollListener(this);//設定滾動監聽


    }


    /**
     * 通知父佈局,佔用的寬和高
     * @param view
     */
    private void measureView(View view) {

        //得到view的佈局寬高
        ViewGroup.LayoutParams vlp = view.getLayoutParams();
        //如果沒有就new一個
        if (vlp==null){
            vlp = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        }
        //ViewGroup.getChildMeasureSpec(int spec,int padding,int childDimension) 1.父view的詳細尺寸 2.view當前尺寸的下的邊距 3.child在當前尺寸下的寬(高)
        int width = ViewGroup.getChildMeasureSpec(0,0,vlp.width);
        int height;
        int tempHeight = vlp.height;
        if (tempHeight>0){
            height = MeasureSpec.makeMeasureSpec(tempHeight,MeasureSpec.EXACTLY);

        }else {
            height =MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);
        }
        view.measure(width, height);

    }


    /**
     * 設定header佈局的上邊距
     * @param topPadding
     */
    private void topPadding(int topPadding) {
        header.setPadding(header.getPaddingLeft(), topPadding, header.getPaddingRight(), header.getPaddingBottom());
        //重繪
        header.invalidate();

    }


    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        this.scrollState =scrollState;
        if(lastVisibleItem==totalItems&&scrollState ==SCROLL_STATE_IDLE){
            //如果不是在載入
           if(!isLoading){
                footer.findViewById(R.id.ll_footer).setVisibility(View.VISIBLE);
               iListener.onLoad();
               isLoading =true;

           }
        }


    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        this.lastVisibleItem=firstVisibleItem+visibleItemCount;
        this.totalItems = totalItemCount;
        this.firstVisibleItem = firstVisibleItem;
    }

    /**
     * 載入更多資料的回撥介面
     */
    public interface ILoadListener {
        public void onLoad();
    }
    //上拉載入完畢
    public void loadCompleted(){
        isLoading =false;
        footer.findViewById(R.id.ll_footer).setVisibility(GONE);
    }


    public void setInterface(ILoadListener iListener){

        this.iListener=iListener;
    }
    /**
     * 下拉重新整理介面
     */
    public interface RLoadListener{
        public void onRefresh();

    }

    public void setReflashInterface(RLoadListener rLoadListener){
        this.rLoadListener =rLoadListener;

    }



    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_DOWN://如果按下
                if (firstVisibleItem==0){
                    isRemark=true;
                    startY = (int) ev.getY();
                }
                break;
            case MotionEvent.ACTION_MOVE:
                onMove(ev);
                break;
            case MotionEvent.ACTION_UP:
                if (state==RELESE){
                    state = REFLASHING;
                    //載入最新資料
                    reflashViewByState();
                    rLoadListener.onRefresh();
                }else if (state==PULL){
                    state = NONE;
                    isRemark = false;
                    reflashViewByState();
                }
                break;


        }

        return super.onTouchEvent(ev);
    }

    /**
     * 判斷移動的過程
     *
     * @param ev
     */
    private void onMove(MotionEvent ev) {
        if (!isRemark){
            return;
        }
        int tempY = (int) ev.getY();
        //記錄滑動距離
        int space = tempY-startY;
        //比較滑動距離和header的高
        int topPadding = space-headerHeight;
        switch (state){
            case NONE:
                if (space>0){
                    state = PULL;
                    reflashViewByState();
                }
                break;
            case PULL:
                //重繪header
                topPadding(topPadding);
                if (space>headerHeight+30&&scrollState==SCROLL_STATE_TOUCH_SCROLL){
                    state =RELESE;
                    reflashViewByState();

                }
                break;
            case RELESE:
                topPadding(topPadding);
                if (space<headerHeight+30){
                    state =PULL;
                    reflashViewByState();
                }else if(space<=0){//如果space<0說明向上滑。所以firstVisibleItem就不是0了 所以將isRemark設定為false;即listView現在不是最頂端的位置
                    state = NONE;
                    isRemark = false;
                    reflashViewByState();
                }
                break;

        }

    }



    /**
     * 根據當前狀態,改變介面顯示
     */
    private void reflashViewByState() {
        TextView tip = (TextView) header.findViewById(R.id.tip);
        ImageView arrow = (ImageView) header.findViewById(R.id.arrow);
        ProgressBar header_pb = (ProgressBar) header.findViewById(R.id.header_pb);
        //設定的下拉箭頭的動畫效果
        RotateAnimation animation = new RotateAnimation(0,180,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
        animation.setDuration(500);
        animation.setFillAfter(true);
        //設定的下拉箭頭的動畫效果
        RotateAnimation animation2 = new RotateAnimation(180,0,RotateAnimation.RELATIVE_TO_SELF,0.5f,RotateAnimation.RELATIVE_TO_SELF,0.5f);
        animation2.setDuration(500);
        animation2.setFillAfter(true);

        /**
         * 四種狀態動畫的改變
         */
        switch (state){
            case NONE:
                arrow.clearAnimation();
                topPadding(-headerHeight);
                break;
            case PULL:
                arrow.setVisibility(View.VISIBLE);
                header_pb.setVisibility(View.GONE);
                tip.setText("下拉可以重新整理!!");
                arrow.clearAnimation();
                arrow.setAnimation(animation2);
                break;
            case RELESE:
                arrow.setVisibility(View.VISIBLE);
                header_pb.setVisibility(View.GONE);
                tip.setText("鬆開可以重新整理!!");
                arrow.clearAnimation();
                arrow.setAnimation(animation);
                break;
            case REFLASHING:
                topPadding(50);
                arrow.setVisibility(View.GONE);
                header_pb.setVisibility(View.VISIBLE);
                tip.setText("正在重新整理...");
                arrow.clearAnimation();
                break;
        }



    }




    /**
     * 獲取完整資料
     *
     */
    public void reflashComplete(){
        state = NONE;
        isRemark = false;
        reflashViewByState();
        //設定重新整理完成的時間
        TextView lasetupdate_time = (TextView) header.findViewById(R.id.tv_lastupdate_time);
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");
        Date date = new Date(System.currentTimeMillis());
        String time = simpleDateFormat.format(date);
        lasetupdate_time.setText(time);



    }

}

MainActivity.java

package com.example.administrator.listviewtest;

import android.os.Handler;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.ArrayAdapter;

import java.util.ArrayList;
import java.util.List;

import java.util.logging.LogRecord;

public class MainActivity extends AppCompatActivity implements LoadListView.ILoadListener,LoadListView.RLoadListener{
    private LoadListView mListView;
    private List<String> datas;
    private ArrayAdapter<String> arrayAdapter;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        setupViews();
        initDatas();
    }

    private void initDatas() {
        for (int i = 0; i < 16; i++) {
            datas.add("ListView的資料"+i+"");
        }
    }
    private void initNewDatas(){
        for (int i = 0; i < 3; i++) {
            datas.add("footer加載出的資料"+i+"");
        }

    }

    private void initREflashDatas() {

        datas.add(0,"下拉重新整理載入的資料");

    }

    private void setupViews() {

        mListView = (LoadListView) findViewById(R.id.lv_main);
        //上拉載入介面
        mListView.setInterface( this);
        mListView.setReflashInterface(this);
        datas = new ArrayList<String>();
        arrayAdapter = new ArrayAdapter<String>(MainActivity.this,android.R.layout.simple_expandable_list_item_1,datas);
        mListView.setAdapter(arrayAdapter);



    }

    //實現onLoad()方法。
    @Override
    public void onLoad() {
        //新增延時效果模擬資料載入
        Handler handler= new Handler() ;
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                initNewDatas();//得到新資料
                arrayAdapter.notifyDataSetChanged();//重新整理ListView;
                mListView.loadCompleted();
            }
        }, 2000);
    }
    //  實現的重新整理方法
    @Override
    public void onRefresh() {
        Handler handler= new Handler() ;
        handler.postDelayed(new Runnable() {
            @Override
            public void run() {
                initREflashDatas();//得到新資料
                arrayAdapter.notifyDataSetChanged();//重新整理ListView;
                mListView.reflashComplete();
            }
        }, 2000);

    }


}

大功告成~~。

附上原始碼記得給好評~:原始碼地址。

Ps:Google其實早已經更新了sdk新增加的一個widget,SwipeRefreshLayout字面意思就是下拉重新整理的佈局,繼承自ViewGroup,可以實現下拉重新整理的操作~~