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


    然后在Android中也逐渐被应用,比如微博,资讯类。
    所以,今天要实现的结果应该也是类似的,先贴出最终完成效果,如下图,接下来我们一步一步实现。

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

->->

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>

代码中在构造函数中添加init()方法加载如下:

    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);}

默认就显示完成了。

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();}

  后面我们就会用到这个mHeaderHeight.

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

    private int mCurrentScrollState;@Overridepublic 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;                         //记录刷新状态@Overridepublic 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;@Overridepublic 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);}//重复贴出下面这段需要注意的代码@Overridepublic 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);//显示向上的箭头}}... ...}

  onScroll里监听到了进入松手刷新状态,onTouchEvent就开始在ACTION_MOVE中处理1/3折扣问题。

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;         //加载数据结束@Overridepublic 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(){@Overridepublic 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;

在其他地方我们就可以不修改这个listview组件的代码,使用如下:

public xxx implements RefreshListener{@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);//类似如下((RefreshListView) listView).setOnRefreshListener(this);}@Overridepublic Object refreshing() {String result = null;//result = FileUtils.readTextFile(file);return result;}@Overridepublic 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() {@Overridepublic 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())));}@Overridepublic 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);}@Overridepublic 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);}}}@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {mCurrentScrollState = scrollState;}@Overridepublic 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(){@Overridepublic 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
    期待有建设性的意见改善这个实现。

转载于:https://www.cnblogs.com/qianxudetianxia/archive/2012/06/16/2549891.html

Android学习系列(30)--App列表之下拉刷新相关推荐

  1. Android学习系列(10)--App列表之拖拽ListView(上)

    研究了很久的拖拽ListView的实现,受益良多,特此与尔共飨.       鉴于这部分内容网上的资料少而简陋,而具体的实现过程或许对大家才有帮助,为了详尽而不失真,我们一步一步分析,分成两篇文章. ...

  2. Android学习系列(15)--App列表之游标ListView(索引ListView)

    游标ListView,提供索引标签,使用户能够快速定位列表项.       也可以叫索引ListView,有的人称也为Tweaked ListView,可能更形象些吧.       一看图啥都懂了: ...

  3. Android学习系列(11)--App列表之拖拽ListView(下)

    接着上篇Android学习系列(10)--App列表之拖拽ListView(上)我们继续实现ListView的拖拽效果. 7.重写onTouchEvent()方法.      在这个方法中我们主要是处 ...

  4. Android学习系列(16)--App列表之圆角ListView

    有些东西看多了,就厌烦了:extjs对我这种感觉最为强烈.甚至,有时觉得设计之殇是审美疲劳. 直角看多了,就想看看圆角,不知何时,这几年刮起了一阵阵的圆角设计风:CSS新标准纳入圆角元素,iphone ...

  5. Android学习系列(7)--App轮询服务器消息

    这篇文章是android开发人员的必备知识. 1.轮询服务器      一般的应用,定时通知消息可以采用轮询的方法从服务器拿取消息,当然实时消息通知的话,建议采用推送服务.     其中需要注意轮询的 ...

  6. Android学习系列(27)--App缓存管理

    随笔- 53 文章- 10 评论- 1064 Android学习系列(27)--App缓存管理 无论大型或小型应用,灵活的缓存可以说不仅大大减轻了服务器的压力,而且因为更快速的用户体验而方便了用户. ...

  7. Android学习系列(34)--App应用之发布各广告平台版本

    Android的广告平台是很多的,各市场对各平台的接受程度是不一样的,Android的开发者如果想集成广告基本要考虑下面两个问题: (1)集成什么广告,会赚钱? (2)集成什么广告,不会被市场拒绝? ...

  8. Android学习系列(22)--App主界面比较

    本文算是一篇漫谈,谈一谈当前几个流行应用的主界面布局,找个经典的布局我们自己也来实现一个. 不是为了追求到底有多难,而是为了明白我们确实需要这么做.  走个题,android的UI差异化市场依然很大, ...

  9. Android学习系列(19)--App离线下载

    宜未雨而绸缪,毋临渴而掘井.----朱用纯<治家格言> 离线下载,在有网络的情况下下载服务器数据,以便无网络时也能阅读,就是离线阅读. 离线下载的功能点如下:       1.下载管理(开 ...

  10. 微信小程序之下拉刷新,上拉更多列表实现

    代码地址如下: http://www.demodashi.com/demo/11110.html 一.准备工作 首先需要下载小程序开发工具 官方下载地址: https://mp.weixin.qq.c ...

最新文章

  1. ESP8266、ESP32 和 ESP32-S2 对比
  2. 微信企业号OAuth2.0授权-Java
  3. java中类加载机制、类加载过程和类加载器层次
  4. 帮助文档的数据库结构
  5. 从一个帖子看部分大学生的学习心态
  6. 使用UGUI绘制自定义几何图形
  7. session、cookie、隐藏域、url参数传递四种会话及跟踪方式
  8. dwf怎么合成一个_将ActionForm拼合成一条插入和更新语句
  9. 获取Linux服务器信息脚本
  10. 音频-音频术语名词解释
  11. 程序员到高级程序员,只需要10个步骤!
  12. H3C 交换机软件版本升级
  13. FileZilla文件下载的目录
  14. ElasticSearch-6.8.11实践笔记
  15. Verilog状态机详述
  16. MySQL索引(什么是索引、如何创建索引、什么时候用索引、索引的作用)
  17. 云计算机账号能锁定一个电脑吗,使用云电脑时我们的账号是否安全?会不会被盗号?...
  18. Codeforces Global Round 15 ABCD
  19. FL studio 20中那些“花里胡哨”的效果器(三)
  20. 矩阵转置相关公式_(机器学习示例)上证指数、深证指数相关性研究

热门文章

  1. 10计算机语言代表什么,win10是什么编程语言写的_win10史诗般的巨型编程项目
  2. 通信协议晦涩难懂搞不定?看完这些动图恍然大悟
  3. android电话通讯录导入iphone6,怎么把小米手机通讯录导入iphone6?
  4. 2016阿里在线笔试Java研发附加题
  5. 软著申请全流程图文解析与注意事项
  6. 近端梯度法(proximal gradient)
  7. iOS小知识: 使用bugly上报自定义错误信息进行数据监控
  8. SVN的正确使用方法以及疑难问题的处理(持续更新与补充)
  9. 9.28 正睿普及3
  10. uniapp微信小程序获取用户登录后openid