Android的ListView是应用最广的一个组件,功能强大,扩展性灵活(不局限于ListView本身一个类),前面的文章有介绍分组,拖拽,3D立体,游标,圆角,而今天我们要介绍的是另外一个扩展ListView:下拉刷新的ListView。
    下拉刷新界面最初流行于iphone应用界面,如图:

android listview数据更新 安卓刷新listview_下拉刷新


    然后在Android中也逐渐被应用,比如微博,资讯类。

    所以,今天要实现的结果应该也是类似的,先贴出最终完成效果,如下图,接下来我们一步一步实现。

android listview数据更新 安卓刷新listview_android_02

 

1. 流程分析
    下拉刷新最主要的流程是:
    (1). 下拉,显示提示头部界面(HeaderView),这个过程提示用户"下拉刷新"
    (2). 下拉到一定程度,超出了刷新最基本的下拉界限,我们认为达到了刷新的条件,提示用户可以"松手刷新"了,效果上允许用户继续下拉
    (3). 用户松手,可能用户下拉远远不止提示头部界面,所以这一步,先反弹回仅显示提示头部界面,然后提示用户"正在加载"。
    (4). 加载完成后,隐藏提示头部界面。
    示意图如下:

android listview数据更新 安卓刷新listview_android_03

->

android listview数据更新 安卓刷新listview_android_04

->

android listview数据更新 安卓刷新listview_加载_05

2. 实现分析
    当前我们要实现上述流程,是基于ListView的,所以对应ListView本身的功能我们来分析一下实现原理:
    (1). 下拉,显示提示头部界面,这个过程提示用户"下拉刷新"
        a. 下拉的操作,首先是监听滚动,ListView提供了onScroll()方法
        b. 与下拉类似一个动作向下飞滑,所以ListView的scrollState有3种值:SCROLL_STATE_IDLE, SCROLL_STATE_TOUCH_SCROLL, SCROLL_STATE_FLING,意思容易理解,而我们要下拉的触发条件是SCROLL_STATE_TOUCH_SCROLL。判断当前的下拉操作状态,ListView提供了public void onScrollStateChanged(AbsListView view, int scrollState) {}。
    c. 下拉的过程中,我们可能还需要下拉到多少的边界值处理,重写onTouchEvent(MotionEvent ev){}方法,可依据ACTION_DOWN,ACTION_MOVE,ACTION_UP实现更精细的判断。
    (2). 下拉到一定程度,超出了刷新最基本的下拉界限,我们认为达到了刷新的条件,提示用户可以"松手刷新"了,效果上允许用户继续下拉
        a. 达到下拉刷新界限,一般指达到header的高度的,所以有两步,第一,获取header的高度,第二,当header.getBottom()>=header的高度时,我们认为就达到了刷新界限值
        b. 继续允许用户下拉,当header完全下拉后,默认无法继续下拉,但是可以增加header的PaddingTop实现这种效果
    (3). 用户松手,可能用户下拉远远不止提示头部界面,所以这一步,先反弹回仅显示提示头部界面,然后提示用户"正在加载"。
        a. 松手后反弹,这个不能一下子弹回去,看上去太突然,需要一步一步柔性的弹回去,像弹簧一样,我们可以new一个Thread循环计算减少PaddingTop,直到PaddingTop为0,反弹结束。
        b. 正在加载,在子线程里处理后台任务
    (4). 加载完成后,隐藏提示头部界面。
        a. 后台任务完成后,我们需要隐藏header,setSelection(1)即实现了从第2项开始显示,间接隐藏了header。
上面我们分析了实现过程的轮廓,接下来,我们通过细节说明和代码具体实现。

3. 初始化
    一切状态显示都是用HeaderView显示的,所以我们需要一个HeaderView的layout,使用addHeaderView方法添加到ListView中。
    同时,默认状态下,HeaderView是不显示的,只是在下拉后才显示,所以我们需要隐藏HeaderView且不影响后续的下拉显示,用setSelection(1)。
    refresh_list_header.xml布局如下:


<?          xml           version          =          "1.0"           encoding          =          "utf-8"          ?>         

          <          LinearLayout           xmlns:android          =          "http://schemas.android.com/apk/res/android"         

                    android:layout_width          =          "fill_parent"         

                    android:layout_height          =          "wrap_content"         

                    android:gravity          =          "center"          >         

                    <          ProgressBar           android:id          =          "@+id/refresh_list_header_progressbar"         

                    android:layout_width          =          "wrap_content"         

                    android:layout_height          =          "wrap_content"         

                    android:layout_gravity          =          "center"         

                    style          =          "?android:attr/progressBarStyleSmall"         

                    android:visibility          =          "gone"          >         

                    </          ProgressBar          >         

                    <          ImageView           android:id          =          "@+id/refresh_list_header_pull_down"         

                    android:layout_width          =          "9dip"         

                    android:layout_height          =          "25dip"         

                    android:layout_gravity          =          "center"         

                    android:src          =          "@drawable/refresh_list_pull_down"           />         

                    <          ImageView           android:id          =          "@+id/refresh_list_header_release_up"         

                    android:layout_width          =          "9dip"         

                    android:layout_height          =          "25dip"         

                    android:layout_gravity          =          "center"         

                    android:src          =          "@drawable/refresh_list_release_up"         

                    android:visibility          =          "gone"           />         

                    <          RelativeLayout           android:layout_width          =          "180dip"         

                    android:layout_height          =          "wrap_content"          >         

                    <          TextView           android:id          =          "@+id/refresh_list_header_text"         

                    android:layout_width          =          "fill_parent"         

                    android:layout_height          =          "wrap_content"         

                    android:gravity          =          "center"         

                    android:layout_alignParentTop          =          "true"         

                    android:textSize          =          "12dip"         

                    android:textColor          =          "#192F06"         

                    android:paddingTop          =          "8dip"         

                    android:text          =          "@string/app_list_header_refresh_down"          />         

                    <          TextView           android:id          =          "@+id/refresh_list_header_last_update"         

                    android:layout_width          =          "fill_parent"         

                    android:layout_height          =          "wrap_content"         

                    android:gravity          =          "center"         

                    android:layout_below          =          "@id/refresh_list_header_text"         

                    android:textSize          =          "12dip"         

                    android:textColor          =          "#192F06"         

                    android:paddingBottom          =          "8dip"         

                    android:text          =          "@string/app_list_header_refresh_last_update"          />         

                    </          RelativeLayout          >         

          </          LinearLayout          >


private           LinearLayout mHeaderLinearLayout =           null          ;         

          private           TextView mHeaderTextView =           null          ;         

          private           TextView mHeaderUpdateText =           null          ;         

          private           ImageView mHeaderPullDownImageView =           null          ;         

          private           ImageView mHeaderReleaseDownImageView =           null          ;         

          private           ProgressBar mHeaderProgressBar =           null          ;         

                    

          public           RefreshListView(Context context) {         

                    this          (context,           null          );         

          }         

          public           RefreshListView(Context context, AttributeSet attrs) {         

                    super          (context, attrs);         

                    init(context);         

          }         

                    

          void           init(          final           Context context) {         

                    mHeaderLinearLayout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.refresh_list_header,           null          );         

                    addHeaderView(mHeaderLinearLayout);         

                    mHeaderTextView = (TextView) findViewById(R.id.refresh_list_header_text);         

                    mHeaderUpdateText = (TextView) findViewById(R.id.refresh_list_header_last_update);         

                    mHeaderPullDownImageView = (ImageView) findViewById(R.id.refresh_list_header_pull_down);         

                    mHeaderReleaseDownImageView = (ImageView) findViewById(R.id.refresh_list_header_release_up);         

                    mHeaderProgressBar = (ProgressBar) findViewById(R.id.refresh_list_header_progressbar);         

                    

                    setSelection(          1          );


    setOnScrollListener(this);


}默认就显示完成了。



4. HeaderView的默认高度测量
    因为下拉到HeaderView全部显示出来,就由提示"下拉刷新"变为"松手刷新",全部显示的出来的测量标准就是header.getBottom()>=header的高度。
    所以,首先我们需要测量HeaderView的默认高度。


//因为是在构造函数里测量高度,应该先measure一下         

          private           void           measureView(View child) {         

                    ViewGroup.LayoutParams p = child.getLayoutParams();         

                    if           (p ==           null          ) {         

                    p =           new           ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,         

                    ViewGroup.LayoutParams.WRAP_CONTENT);         

                    }         

                    

                    int           childWidthSpec = ViewGroup.getChildMeasureSpec(          0          ,           0           +           0          , p.width);         

                    int           lpHeight = p.height;         

                    int           childHeightSpec;         

                    if           (lpHeight >           0          ) {         

                    childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,         

                    MeasureSpec.EXACTLY);         

                    }           else           {         

                    childHeightSpec = MeasureSpec.makeMeasureSpec(          0          ,         

                    MeasureSpec.UNSPECIFIED);         

                    }         

                    child.measure(childWidthSpec, childHeightSpec);         

          }


    然后在init的上述代码后面加上调用measureView后,使用getMeasureHeight()方法获取header的高度:


private           int           mHeaderHeight;         

          void           init(          final           Context context) {         

                    ... ...         

                    measureView(mHeaderLinearLayout);         

                    mHeaderHeight = mHeaderLinearLayout.getMeasuredHeight();         

          }


5. scrollState监听记录
    scrollState有3种,使用onScrollStateChanged()方法监听记录。


private           int           mCurrentScrollState;         

          @Override         

          public           void           onScrollStateChanged(AbsListView view,           int           scrollState) {         

                    mCurrentScrollState = scrollState;         

          }


    然后即可使用mCurrentScrollState作为后面判断的条件了。

6. 刷新状态分析
    因为一些地方需要知道我们处在正常状态下还是进入下拉刷新状态还是松手反弹状态,比如,
    (1). 在非正常的状态下,我们不小心飞滑了一下(松手的瞬间容易出现这种情况),我们不能setSelection(1)的,否则总是松手后header跳的一下消失掉了。
    (2). 下拉后要做一个下拉效果的特殊处理,需要用到OVER_PULL_REFRESH(松手刷新状态下)
    (3). 松手反弹后要做一个反弹效果的特殊处理,需要用到OVER_PULL_REFRESH和ENTER_PULL_REFRESH。


private           final           static           int           NONE_PULL_REFRESH =           0          ;             //正常状态         

          private           final           static           int           ENTER_PULL_REFRESH =           1          ;            //进入下拉刷新状态         

          private           final           static           int           OVER_PULL_REFRESH =           2          ;             //进入松手刷新状态         

          private           final           static           int           EXIT_PULL_REFRESH =           3          ;               //松手后反弹后加载状态         

          private           int           mPullRefreshState =           0          ;                                   //记录刷新状态         

          @Override         

          public           void           onScroll(AbsListView view,           int           firstVisibleItem,           int           visibleItemCount,           int           totalItemCount) {         

                    if           (mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL         

                    && firstVisibleItem ==           0         

                    && (mHeaderLinearLayout.getBottom() >=           0           && mHeaderLinearLayout.getBottom() < mHeaderHeight)) {         

                    //进入且仅进入下拉刷新状态         

                    if           (mPullRefreshState == NONE_PULL_REFRESH) {         

                    mPullRefreshState = ENTER_PULL_REFRESH;         

                    }         

                    }           else           if           (mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL         

                    && firstVisibleItem ==           0         

                    && (mHeaderLinearLayout.getBottom() >= mHeaderHeight)) {         

                    //下拉达到界限,进入松手刷新状态         

                    if           (mPullRefreshState == ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH) {         

                    mPullRefreshState = OVER_PULL_REFRESH;         

                    //下面是进入松手刷新状态需要做的一个显示改变         

                    mDownY = mMoveY;          //用于后面的下拉特殊效果         

                    mHeaderTextView.setText(          "松手刷新"          );         

                    mHeaderPullDownImageView.setVisibility(View.GONE);         

                    mHeaderReleaseDownImageView.setVisibility(View.VISIBLE);         

                    }         

                    }           else           if           (mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem !=           0          ) {         

                    //不刷新了         

                    if           (mPullRefreshState == ENTER_PULL_REFRESH) {         

                    mPullRefreshState = NONE_PULL_REFRESH;         

                    }         

                    }           else           if           (mCurrentScrollState == SCROLL_STATE_FLING && firstVisibleItem ==           0          ) {         

                    //飞滑状态,不能显示出header,也不能影响正常的飞滑         

                    //只在正常情况下才纠正位置         

                    if           (mPullRefreshState == NONE_PULL_REFRESH) {         

                    setSelection(          1          );         

                    }         

                    }         

          }


  mPullRefreshState将是后面我们处理边界的重要变量。

6. 下拉效果的特殊处理
    所谓的特殊处理,当header完全显示后,下拉只按下拉1/3的距离下拉,给用户一种艰难下拉,该松手的弹簧感觉。
    这个在onTouchEvent里处理比较方便:


private           float           mDownY;         

          private           float           mMoveY;         

          @Override         

          public           boolean           onTouchEvent(MotionEvent ev) {         

                    switch           (ev.getAction()) {         

                    case           MotionEvent.ACTION_DOWN:         

                    //记下按下位置         

                    //改变         

                    mDownY = ev.getY();         

                    break          ;         

                    case           MotionEvent.ACTION_MOVE:         

                    //移动时手指的位置         

                    mMoveY = ev.getY();         

                    if           (mPullRefreshState == OVER_PULL_REFRESH) {         

                    //注意下面的mDownY在onScroll的第二个else中被改变了         

                    mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),         

                    (          int          )((mMoveY - mDownY)/          3          ),           //1/3距离折扣         

                    mHeaderLinearLayout.getPaddingRight(),         

                    mHeaderLinearLayout.getPaddingBottom());         

                    }         

                    break          ;         

                    case           MotionEvent.ACTION_UP:         

                    ... ...         

                    break          ;         

                    }         

                    return           super          .onTouchEvent(ev);         

          }         

                    

          //重复贴出下面这段需要注意的代码         

          @Override         

          public           void           onScroll(AbsListView view,           int           firstVisibleItem,           int           visibleItemCount,           int           totalItemCount) {         

                    ... ...         

                    else           if           (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL         

                    && firstVisibleItem ==           0         

                    && (mHeaderLinearLayout.getBottom() >= mHeaderHeight)) {         

                    //下拉达到界限,进入松手刷新状态         

                    if           (mPullRefreshState == ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH) {         

                    mPullRefreshState = OVER_PULL_REFRESH;         

                    mDownY = mMoveY;           //为下拉1/3折扣效果记录开始位置         

                    mHeaderTextView.setText(          "松手刷新"          );          //显示松手刷新         

                    mHeaderPullDownImageView.setVisibility(View.GONE);          //隐藏"下拉刷新"         

                    mHeaderReleaseDownImageView.setVisibility(View.VISIBLE);          //显示向上的箭头         

                    }         

                    }         

                    ... ...         

          }


7. 反弹效果的特殊处理
    松手后我们需要一个柔性的反弹效果,意味着我们弹回去的过程需要分一步步走,我的解决方案是:
    在子线程里计算PaddingTop,并减少到原来的3/4,循环通知主线程,直到PaddingTop小于1(这个值取一个小值,合适即可)。
    松手后,当然是在onTouchEvent的ACTION_UP条件下处理比较方便:


//因为涉及到handler数据处理,为方便我们定义如下常量         

          private           final           static           int           REFRESH_BACKING =           0          ;                //反弹中         

          private           final           static           int           REFRESH_BACED =           1          ;                  //达到刷新界限,反弹结束后         

          private           final           static           int           REFRESH_RETURN =           2          ;                 //没有达到刷新界限,返回         

          private           final           static           int           REFRESH_DONE =           3          ;                   //加载数据结束         

                    

          @Override         

          public           boolean           onTouchEvent(MotionEvent ev) {         

                    switch           (ev.getAction()) {         

                    ... ...         

                    case           MotionEvent.ACTION_UP:         

                    //when you action up, it will do these:         

                    //1. roll back util header topPadding is 0         

                    //2. hide the header by setSelection(1)         

                    if           (mPullRefreshState == OVER_PULL_REFRESH || mPullRefreshState == ENTER_PULL_REFRESH) {         

                    new           Thread() {         

                    public           void           run() {         

                    Message msg;         

                    while          (mHeaderLinearLayout.getPaddingTop() >           1          ) {         

                    msg = mHandler.obtainMessage();         

                    msg.what = REFRESH_BACKING;         

                    mHandler.sendMessage(msg);         

                    try           {         

                    sleep(          5          );          //慢一点反弹,别一下子就弹回去了         

                    }           catch           (InterruptedException e) {         

                    e.printStackTrace();         

                    }         

                    }         

                    msg = mHandler.obtainMessage();         

                    if           (mPullRefreshState == OVER_PULL_REFRESH) {         

                    msg.what = REFRESH_BACED;          //加载数据完成,结束返回         

                    }           else           {         

                    msg.what = REFRESH_RETURN;          //未达到刷新界限,直接返回         

                    }         

                    mHandler.sendMessage(msg);         

                    };         

                    }.start();         

                    }         

                    break          ;         

                    }         

                    return           super          .onTouchEvent(ev);         

          }         

                    

          private           Handler mHandler =           new           Handler(){         

                    @Override         

                    public           void           handleMessage(Message msg) {         

                    switch           (msg.what) {         

                    case           REFRESH_BACKING:         

                    mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),         

                    (          int          ) (mHeaderLinearLayout.getPaddingTop()*          0          .75f),         

                    mHeaderLinearLayout.getPaddingRight(),         

                    mHeaderLinearLayout.getPaddingBottom());         

                    break          ;         

                    case           REFRESH_BACED:         

                    mHeaderTextView.setText(          "正在加载..."          );         

                    mHeaderProgressBar.setVisibility(View.VISIBLE);         

                    mHeaderPullDownImageView.setVisibility(View.GONE);         

                    mHeaderReleaseDownImageView.setVisibility(View.GONE);         

                    mPullRefreshState = EXIT_PULL_REFRESH;         

                    new           Thread() {         

                    public           void           run() {         

                    sleep(          2000          );          //处理后台加载数据         

                    Message msg = mHandler.obtainMessage();         

                    msg.what = REFRESH_DONE;         

                    //通知主线程加载数据完成         

                    mHandler.sendMessage(msg);         

                    };         

                    }.start();         

                    break          ;         

                    case           REFRESH_RETURN:         

                    //未达到刷新界限,返回         

                    mHeaderTextView.setText(          "下拉刷新"          );         

                    mHeaderProgressBar.setVisibility(View.INVISIBLE);         

                    mHeaderPullDownImageView.setVisibility(View.VISIBLE);         

                    mHeaderReleaseDownImageView.setVisibility(View.GONE);         

                    mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),         

                    0          ,         

                    mHeaderLinearLayout.getPaddingRight(),         

                    mHeaderLinearLayout.getPaddingBottom());         

                    mPullRefreshState = NONE_PULL_REFRESH;         

                    setSelection(          1          );         

                    break          ;         

                    case           REFRESH_DONE:         

                    //刷新结束后,恢复原始默认状态         

                    mHeaderTextView.setText(          "下拉刷新"          );         

                    mHeaderProgressBar.setVisibility(View.INVISIBLE);         

                    mHeaderPullDownImageView.setVisibility(View.VISIBLE);         

                    mHeaderReleaseDownImageView.setVisibility(View.GONE);         

                    mHeaderUpdateText.setText(getContext().getString(R.string.app_list_header_refresh_last_update,          

                    mSimpleDateFormat.format(          new           Date())));         

                    mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),         

                    0          ,         

                    mHeaderLinearLayout.getPaddingRight(),         

                    mHeaderLinearLayout.getPaddingBottom());         

                    mPullRefreshState = NONE_PULL_REFRESH;         

                    setSelection(          1          );         

                    break          ;         

                    default          :         

                    break          ;         

                    }         

                    }         

          };


8. 切入数据加载过程
    上面数据后台处理我们用sleep(2000)来处理,实际处理中,作为公共组件,我们也不好把具体代码直接写在这里,我们需要一个更灵活的分离:
    (1). 定义接口
    (2). 注入接口


//定义接口         

          public           interface           RefreshListener {         

                    Object refreshing();                          //加载数据         

                    void           refreshed(Object obj);              //外部可扩展加载完成后的操作         

          }         

                    

          //注入接口         

          private           Object mRefreshObject =           null          ;           //传值         

          private           RefreshListener mRefreshListener =           null          ;         

          public           void           setOnRefreshListener(RefreshListener refreshListener) {         

                    this          .mRefreshListener = refreshListener;         

          }         

                    

                    

          //我们需要重写上面的mHandler如下代码         

          case           REFRESH_BACED:         

                    ... ...         

                    new           Thread() {         

                    public           void           run() {         

                    if           (mRefreshListener !=           null          ) {         

                    mRefreshObject = mRefreshListener.refreshing();         

                    }         

                    Message msg = mHandler.obtainMessage();         

                    msg.what = REFRESH_DONE;         

                    mHandler.sendMessage(msg);         

                    };         

                    }.start();         

                    break          ;         

          case           REFRESH_DONE:         

                    ... ...         

                    mPullRefreshState = NONE_PULL_REFRESH;         

                    setSelection(          1          );         

                    if           (mRefreshListener !=           null          ) {         

                    mRefreshListener.refreshed(mRefreshObject);         

                    }         

                    break          ;


public           xxx           implements           RefreshListener{         

                    

          @Override         

                    protected           void           onCreate(Bundle savedInstanceState) {         

                    super          .onCreate(savedInstanceState);         

                    //类似如下         

                    ((RefreshListView) listView).setOnRefreshListener(          this          );         

                    }         

                    

                    @Override         

                    public           Object refreshing() {         

                    String result =           null          ;         

                    //result = FileUtils.readTextFile(file);         

                    return           result;         

                    }         

                    

                    @Override         

                    public           void           refreshed(Object obj) {         

                    if           (obj !=           null          ) {         

                    //扩展操作         

                    }         

                    };         

          }


9. 扩展"更多"功能
    下拉刷新之外,我们也可以通过相同方法使用FooterView切入底部"更多"过程,这里我就不详细说明了

10. 源码
    上面的每段代码都看做是"零部件",需要组合一下。
    因为我们上面实现了下拉刷新,还增加了"更多"功能,我们直接命名这个类为RefreshListView吧:


package           com.tianxia.lib.baseworld.widget;         

                    

          import           java.text.SimpleDateFormat;         

          import           java.util.Date;         

                    

          import           android.content.Context;         

          import           android.os.Handler;         

          import           android.os.Message;         

          import           android.util.AttributeSet;         

          import           android.view.LayoutInflater;         

          import           android.view.MotionEvent;         

          import           android.view.View;         

          import           android.view.ViewGroup;         

          import           android.widget.AbsListView;         

          import           android.widget.AbsListView.OnScrollListener;         

          import           android.widget.ImageView;         

          import           android.widget.LinearLayout;         

          import           android.widget.ListAdapter;         

          import           android.widget.ListView;         

          import           android.widget.ProgressBar;         

          import           android.widget.TextView;         

                    

          import           com.tianxia.lib.baseworld.R;         

                    

          /**         

                    * 下拉刷新,底部更多         

                    *         

                    */         

          public           class           RefreshListView           extends           ListView           implements           OnScrollListener{         

                    

                    private           float           mDownY;         

                    private           float           mMoveY;         

                    

                    private           int           mHeaderHeight;         

                    

                    private           int           mCurrentScrollState;         

                    

                    private           final           static           int           NONE_PULL_REFRESH =           0          ;              //正常状态         

                    private           final           static           int           ENTER_PULL_REFRESH =           1          ;             //进入下拉刷新状态         

                    private           final           static           int           OVER_PULL_REFRESH =           2          ;              //进入松手刷新状态         

                    private           final           static           int           EXIT_PULL_REFRESH =           3          ;              //松手后反弹和加载状态         

                    private           int           mPullRefreshState =           0          ;                           //记录刷新状态         

                    

                    private           final           static           int           REFRESH_BACKING =           0          ;                //反弹中         

                    private           final           static           int           REFRESH_BACED =           1          ;                  //达到刷新界限,反弹结束后         

                    private           final           static           int           REFRESH_RETURN =           2          ;                 //没有达到刷新界限,返回         

                    private           final           static           int           REFRESH_DONE =           3          ;                   //加载数据结束         

                    

                    private           LinearLayout mHeaderLinearLayout =           null          ;         

                    private           LinearLayout mFooterLinearLayout =           null          ;         

                    private           TextView mHeaderTextView =           null          ;         

                    private           TextView mHeaderUpdateText =           null          ;         

                    private           ImageView mHeaderPullDownImageView =           null          ;         

                    private           ImageView mHeaderReleaseDownImageView =           null          ;         

                    private           ProgressBar mHeaderProgressBar =           null          ;         

                    private           TextView mFooterTextView =           null          ;         

                    private           ProgressBar mFooterProgressBar =           null          ;         

                    

                    private           SimpleDateFormat mSimpleDateFormat;         

                    

                    private           Object mRefreshObject =           null          ;         

                    private           RefreshListener mRefreshListener =           null          ;         

                    public           void           setOnRefreshListener(RefreshListener refreshListener) {         

                    this          .mRefreshListener = refreshListener;         

                    }         

                    

                    public           RefreshListView(Context context) {         

                    this          (context,           null          );         

                    }         

                    

                    public           RefreshListView(Context context, AttributeSet attrs) {         

                    super          (context, attrs);         

                    init(context);         

                    }         

                    

                    void           init(          final           Context context) {         

                    mHeaderLinearLayout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.refresh_list_header,           null          );         

                    addHeaderView(mHeaderLinearLayout);         

                    mHeaderTextView = (TextView) findViewById(R.id.refresh_list_header_text);         

                    mHeaderUpdateText = (TextView) findViewById(R.id.refresh_list_header_last_update);         

                    mHeaderPullDownImageView = (ImageView) findViewById(R.id.refresh_list_header_pull_down);         

                    mHeaderReleaseDownImageView = (ImageView) findViewById(R.id.refresh_list_header_release_up);         

                    mHeaderProgressBar = (ProgressBar) findViewById(R.id.refresh_list_header_progressbar);         

                    

                    mFooterLinearLayout = (LinearLayout) LayoutInflater.from(context).inflate(R.layout.refresh_list_footer,           null          );         

                    addFooterView(mFooterLinearLayout);         

                    mFooterProgressBar = (ProgressBar) findViewById(R.id.refresh_list_footer_progressbar);         

                    mFooterTextView = (TextView) mFooterLinearLayout.findViewById(R.id.refresh_list_footer_text);         

                    mFooterLinearLayout.setOnClickListener(          new           OnClickListener() {         

                    @Override         

                    public           void           onClick(View v) {         

                    if           (context.getString(R.string.app_list_footer_more).equals(mFooterTextView.getText())) {         

                    mFooterTextView.setText(R.string.app_list_footer_loading);         

                    mFooterProgressBar.setVisibility(View.VISIBLE);         

                    if           (mRefreshListener !=           null          ) {         

                    mRefreshListener.more();         

                    }         

                    }         

                    }         

                    });         

                    

                    setSelection(          1          );         

                    setOnScrollListener(          this          );         

                    measureView(mHeaderLinearLayout);         

                    mHeaderHeight = mHeaderLinearLayout.getMeasuredHeight();         

                    

                    mSimpleDateFormat =           new           SimpleDateFormat(          "yyyy-MM-dd hh:mm"          );         

                    mHeaderUpdateText.setText(context.getString(R.string.app_list_header_refresh_last_update, mSimpleDateFormat.format(          new           Date())));         

                    }         

                    

                    @Override         

                    public           boolean           onTouchEvent(MotionEvent ev) {         

                    switch           (ev.getAction()) {         

                    case           MotionEvent.ACTION_DOWN:         

                    mDownY = ev.getY();         

                    break          ;         

                    case           MotionEvent.ACTION_MOVE:         

                    mMoveY = ev.getY();         

                    if           (mPullRefreshState == OVER_PULL_REFRESH) {         

                    mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),         

                    (          int          )((mMoveY - mDownY)/          3          ),         

                    mHeaderLinearLayout.getPaddingRight(),         

                    mHeaderLinearLayout.getPaddingBottom());         

                    }         

                    break          ;         

                    case           MotionEvent.ACTION_UP:         

                    //when you action up, it will do these:         

                    //1. roll back util header topPadding is 0         

                    //2. hide the header by setSelection(1)         

                    if           (mPullRefreshState == OVER_PULL_REFRESH || mPullRefreshState == ENTER_PULL_REFRESH) {         

                    new           Thread() {         

                    public           void           run() {         

                    Message msg;         

                    while          (mHeaderLinearLayout.getPaddingTop() >           1          ) {         

                    msg = mHandler.obtainMessage();         

                    msg.what = REFRESH_BACKING;         

                    mHandler.sendMessage(msg);         

                    try           {         

                    sleep(          5          );         

                    }           catch           (InterruptedException e) {         

                    e.printStackTrace();         

                    }         

                    }         

                    msg = mHandler.obtainMessage();         

                    if           (mPullRefreshState == OVER_PULL_REFRESH) {         

                    msg.what = REFRESH_BACED;         

                    }           else           {         

                    msg.what = REFRESH_RETURN;         

                    }         

                    mHandler.sendMessage(msg);         

                    };         

                    }.start();         

                    }         

                    break          ;         

                    }         

                    return           super          .onTouchEvent(ev);         

                    }         

                    

                    @Override         

                    public           void           onScroll(AbsListView view,           int           firstVisibleItem,           int           visibleItemCount,           int           totalItemCount) {         

                    if           (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL         

                    && firstVisibleItem ==           0         

                    && (mHeaderLinearLayout.getBottom() >=           0           && mHeaderLinearLayout.getBottom() < mHeaderHeight)) {         

                    //进入且仅进入下拉刷新状态         

                    if           (mPullRefreshState == NONE_PULL_REFRESH) {         

                    mPullRefreshState = ENTER_PULL_REFRESH;         

                    }         

                    }           else           if           (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL         

                    && firstVisibleItem ==           0         

                    && (mHeaderLinearLayout.getBottom() >= mHeaderHeight)) {         

                    //下拉达到界限,进入松手刷新状态         

                    if           (mPullRefreshState == ENTER_PULL_REFRESH || mPullRefreshState == NONE_PULL_REFRESH) {         

                    mPullRefreshState = OVER_PULL_REFRESH;         

                    mDownY = mMoveY;           //为下拉1/3折扣效果记录开始位置         

                    mHeaderTextView.setText(          "松手刷新"          );          //显示松手刷新         

                    mHeaderPullDownImageView.setVisibility(View.GONE);          //隐藏"下拉刷新"         

                    mHeaderReleaseDownImageView.setVisibility(View.VISIBLE);          //显示向上的箭头         

                    }         

                    }           else           if           (mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem !=           0          ) {         

                    //不刷新了         

                    if           (mPullRefreshState == ENTER_PULL_REFRESH) {         

                    mPullRefreshState = NONE_PULL_REFRESH;         

                    }         

                    }           else           if           (mCurrentScrollState == SCROLL_STATE_FLING && firstVisibleItem ==           0          ) {         

                    //飞滑状态,不能显示出header,也不能影响正常的飞滑         

                    //只在正常情况下才纠正位置         

                    if           (mPullRefreshState == NONE_PULL_REFRESH) {         

                    setSelection(          1          );         

                    }         

                    }         

                    }         

                    

                    @Override         

                    public           void           onScrollStateChanged(AbsListView view,           int           scrollState) {         

                    mCurrentScrollState = scrollState;         

                    }         

                    

                    @Override         

                    public           void           setAdapter(ListAdapter adapter) {         

                    super          .setAdapter(adapter);         

                    setSelection(          1          );         

                    }         

                    

                    private           void           measureView(View child) {         

                    ViewGroup.LayoutParams p = child.getLayoutParams();         

                    if           (p ==           null          ) {         

                    p =           new           ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,         

                    ViewGroup.LayoutParams.WRAP_CONTENT);         

                    }         

                    

                    int           childWidthSpec = ViewGroup.getChildMeasureSpec(          0          ,           0           +           0          , p.width);         

                    int           lpHeight = p.height;         

                    int           childHeightSpec;         

                    if           (lpHeight >           0          ) {         

                    childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,         

                    MeasureSpec.EXACTLY);         

                    }           else           {         

                    childHeightSpec = MeasureSpec.makeMeasureSpec(          0          ,         

                    MeasureSpec.UNSPECIFIED);         

                    }         

                    child.measure(childWidthSpec, childHeightSpec);         

                    }         

                    

                    private           Handler mHandler =           new           Handler(){         

                    @Override         

                    public           void           handleMessage(Message msg) {         

                    switch           (msg.what) {         

                    case           REFRESH_BACKING:         

                    mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),         

                    (          int          ) (mHeaderLinearLayout.getPaddingTop()*          0          .75f),         

                    mHeaderLinearLayout.getPaddingRight(),         

                    mHeaderLinearLayout.getPaddingBottom());         

                    break          ;         

                    case           REFRESH_BACED:         

                    mHeaderTextView.setText(          "正在加载..."          );         

                    mHeaderProgressBar.setVisibility(View.VISIBLE);         

                    mHeaderPullDownImageView.setVisibility(View.GONE);         

                    mHeaderReleaseDownImageView.setVisibility(View.GONE);         

                    mPullRefreshState = EXIT_PULL_REFRESH;         

                    new           Thread() {         

                    public           void           run() {         

                    if           (mRefreshListener !=           null          ) {         

                    mRefreshObject = mRefreshListener.refreshing();         

                    }         

                    Message msg = mHandler.obtainMessage();         

                    msg.what = REFRESH_DONE;         

                    mHandler.sendMessage(msg);         

                    };         

                    }.start();         

                    break          ;         

                    case           REFRESH_RETURN:         

                    mHeaderTextView.setText(          "下拉刷新"          );         

                    mHeaderProgressBar.setVisibility(View.INVISIBLE);         

                    mHeaderPullDownImageView.setVisibility(View.VISIBLE);         

                    mHeaderReleaseDownImageView.setVisibility(View.GONE);         

                    mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),         

                    0          ,         

                    mHeaderLinearLayout.getPaddingRight(),         

                    mHeaderLinearLayout.getPaddingBottom());         

                    mPullRefreshState = NONE_PULL_REFRESH;         

                    setSelection(          1          );         

                    break          ;         

                    case           REFRESH_DONE:         

                    mHeaderTextView.setText(          "下拉刷新"          );         

                    mHeaderProgressBar.setVisibility(View.INVISIBLE);         

                    mHeaderPullDownImageView.setVisibility(View.VISIBLE);         

                    mHeaderReleaseDownImageView.setVisibility(View.GONE);         

                    mHeaderUpdateText.setText(getContext().getString(R.string.app_list_header_refresh_last_update,         

                    mSimpleDateFormat.format(          new           Date())));         

                    mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),         

                    0          ,         

                    mHeaderLinearLayout.getPaddingRight(),         

                    mHeaderLinearLayout.getPaddingBottom());         

                    mPullRefreshState = NONE_PULL_REFRESH;         

                    setSelection(          1          );         

                    if           (mRefreshListener !=           null          ) {         

                    mRefreshListener.refreshed(mRefreshObject);         

                    }         

                    break          ;         

                    default          :         

                    break          ;         

                    }         

                    }         

                    };         

                    public           interface           RefreshListener {         

                    Object refreshing();         

                    void           refreshed(Object obj);         

                    void           more();         

                    }         

                    

                    public           void           finishFootView() {         

                    mFooterProgressBar.setVisibility(View.GONE);         

                    mFooterTextView.setText(R.string.app_list_footer_more);         

                    }         

                    

                    public           void           addFootView() {         

                    if           (getFooterViewsCount() ==           0          ) {         

                    addFooterView(mFooterLinearLayout);         

                    }         

                    }         

                    

                    public           void           removeFootView() {         

                    removeFooterView(mFooterLinearLayout);         

                    }         

          }

11.小结

    这个只是一个原型,无论代码风格和逻辑处理
    以下是例子效果
    https://github.com/openproject/world/blob/master/baseworld/src/com/tianxia/lib/baseworld/widget/RefreshListView.java
    https://github.com/openproject/world/blob/master/healthworld/src/com/tianxia/app/healthworld/infomation/InfomationTabActivity.java
    期待有建设性的意见改善这个实现。