android中listView下拉刷新
 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布局如下:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | <?xmlversion="1.0"encoding="utf-8"?>    android:layout_width="fill_parent"    android:layout_height="wrap_content"    android:gravity="center">    <ProgressBarandroid: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>    <ImageViewandroid: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"/>    <ImageViewandroid: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"/>    <RelativeLayoutandroid:layout_width="180dip"        android:layout_height="wrap_content">        <TextViewandroid: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"/>        <TextViewandroid: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> | 
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | privateLinearLayout mHeaderLinearLayout = null;privateTextView mHeaderTextView = null;privateTextView mHeaderUpdateText = null;privateImageView mHeaderPullDownImageView = null;privateImageView mHeaderReleaseDownImageView = null;privateProgressBar mHeaderProgressBar = null;publicRefreshListView(Context context) {    this(context, null);}publicRefreshListView(Context context, AttributeSet attrs) {    super(context, attrs);    init(context);}voidinit(finalContext 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的默认高度。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | //因为是在构造函数里测量高度,应该先measure一下privatevoidmeasureView(View child) {    ViewGroup.LayoutParams p = child.getLayoutParams();    if(p == null) {        p = newViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,                ViewGroup.LayoutParams.WRAP_CONTENT);    }    intchildWidthSpec = ViewGroup.getChildMeasureSpec(0, 0+ 0, p.width);    intlpHeight = p.height;    intchildHeightSpec;    if(lpHeight > 0) {        childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,                MeasureSpec.EXACTLY);    } else{        childHeightSpec = MeasureSpec.makeMeasureSpec(0,                MeasureSpec.UNSPECIFIED);    }    child.measure(childWidthSpec, childHeightSpec);} | 
然后在init的上述代码后面加上调用measureView后,使用getMeasureHeight()方法获取header的高度:
| 1 2 3 4 5 6 | privateintmHeaderHeight;voidinit(finalContext context) {    ... ...    measureView(mHeaderLinearLayout);    mHeaderHeight = mHeaderLinearLayout.getMeasuredHeight();} | 
5. scrollState监听记录
    scrollState有3种,使用onScrollStateChanged()方法监听记录。
| 1 2 3 4 5 | privateintmCurrentScrollState;@OverridepublicvoidonScrollStateChanged(AbsListView view, intscrollState) {    mCurrentScrollState = scrollState;} | 
然后即可使用mCurrentScrollState作为后面判断的条件了。
6. 刷新状态分析
    因为一些地方需要知道我们处在正常状态下还是进入下拉刷新状态还是松手反弹状态,比如,
    (1). 在非正常的状态下,我们不小心飞滑了一下(松手的瞬间容易出现这种情况),我们不能setSelection(1)的,否则总是松手后header跳的一下消失掉了。
    (2). 下拉后要做一个下拉效果的特殊处理,需要用到OVER_PULL_REFRESH(松手刷新状态下)
    (3). 松手反弹后要做一个反弹效果的特殊处理,需要用到OVER_PULL_REFRESH和ENTER_PULL_REFRESH。
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | privatefinalstaticintNONE_PULL_REFRESH = 0;   //正常状态privatefinalstaticintENTER_PULL_REFRESH = 1;  //进入下拉刷新状态privatefinalstaticintOVER_PULL_REFRESH = 2;   //进入松手刷新状态privatefinalstaticintEXIT_PULL_REFRESH = 3;     //松手后反弹后加载状态privateintmPullRefreshState = 0;                         //记录刷新状态@OverridepublicvoidonScroll(AbsListView view, intfirstVisibleItem, intvisibleItemCount, inttotalItemCount) {    if(mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL            && firstVisibleItem == 0            && (mHeaderLinearLayout.getBottom() >= 0&& mHeaderLinearLayout.getBottom() < mHeaderHeight)) {        //进入且仅进入下拉刷新状态        if(mPullRefreshState == NONE_PULL_REFRESH) {            mPullRefreshState = ENTER_PULL_REFRESH;        }    } elseif(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);        }    } elseif(mCurrentScrollState ==SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem != 0) {        //不刷新了        if(mPullRefreshState == ENTER_PULL_REFRESH) {            mPullRefreshState = NONE_PULL_REFRESH;        }    } elseif(mCurrentScrollState == SCROLL_STATE_FLING && firstVisibleItem == 0) {        //飞滑状态,不能显示出header,也不能影响正常的飞滑        //只在正常情况下才纠正位置        if(mPullRefreshState == NONE_PULL_REFRESH) {            setSelection(1);        }    }} | 
mPullRefreshState将是后面我们处理边界的重要变量。
6. 下拉效果的特殊处理
    所谓的特殊处理,当header完全显示后,下拉只按下拉1/3的距离下拉,给用户一种艰难下拉,该松手的弹簧感觉。
    这个在onTouchEvent里处理比较方便:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | privatefloatmDownY;privatefloatmMoveY;@OverridepublicbooleanonTouchEvent(MotionEvent ev) {    switch(ev.getAction()) {        caseMotionEvent.ACTION_DOWN:            //记下按下位置            //改变            mDownY = ev.getY();            break;        caseMotionEvent.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;        caseMotionEvent.ACTION_UP:            ... ...            break;    }    returnsuper.onTouchEvent(ev);}//重复贴出下面这段需要注意的代码@OverridepublicvoidonScroll(AbsListView view, intfirstVisibleItem, intvisibleItemCount, inttotalItemCount) {    ... ...    elseif(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条件下处理比较方便:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 | //因为涉及到handler数据处理,为方便我们定义如下常量privatefinalstaticintREFRESH_BACKING = 0;      //反弹中privatefinalstaticintREFRESH_BACED = 1;        //达到刷新界限,反弹结束后privatefinalstaticintREFRESH_RETURN = 2;       //没有达到刷新界限,返回privatefinalstaticintREFRESH_DONE = 3;         //加载数据结束@OverridepublicbooleanonTouchEvent(MotionEvent ev) {    switch(ev.getAction()) {        ... ...        caseMotionEvent.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) {                newThread() {                    publicvoidrun() {                        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;    }    returnsuper.onTouchEvent(ev);}privateHandler mHandler = newHandler(){    @Override    publicvoidhandleMessage(Message msg) {        switch(msg.what) {        caseREFRESH_BACKING:            mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),                    (int) (mHeaderLinearLayout.getPaddingTop()*0.75f),                    mHeaderLinearLayout.getPaddingRight(),                    mHeaderLinearLayout.getPaddingBottom());            break;        caseREFRESH_BACED:            mHeaderTextView.setText("正在加载...");            mHeaderProgressBar.setVisibility(View.VISIBLE);            mHeaderPullDownImageView.setVisibility(View.GONE);            mHeaderReleaseDownImageView.setVisibility(View.GONE);            mPullRefreshState = EXIT_PULL_REFRESH;            newThread() {                publicvoidrun() {                    sleep(2000);//处理后台加载数据                    Message msg = mHandler.obtainMessage();                    msg.what = REFRESH_DONE;                    //通知主线程加载数据完成                    mHandler.sendMessage(msg);                };            }.start();            break;        caseREFRESH_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;        caseREFRESH_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(newDate())));            mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),                    0,                    mHeaderLinearLayout.getPaddingRight(),                    mHeaderLinearLayout.getPaddingBottom());            mPullRefreshState = NONE_PULL_REFRESH;            setSelection(1);            break;        default:            break;        }    }}; | 
8. 切入数据加载过程
    上面数据后台处理我们用sleep(2000)来处理,实际处理中,作为公共组件,我们也不好把具体代码直接写在这里,我们需要一个更灵活的分离:
    (1). 定义接口
    (2). 注入接口
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | //定义接口publicinterfaceRefreshListener {    Object refreshing();                //加载数据    voidrefreshed(Object obj);    //外部可扩展加载完成后的操作}//注入接口privateObject mRefreshObject = null; //传值privateRefreshListener mRefreshListener = null;publicvoidsetOnRefreshListener(RefreshListener refreshListener) {    this.mRefreshListener = refreshListener;}//我们需要重写上面的mHandler如下代码caseREFRESH_BACED:    ... ...    newThread() {        publicvoidrun() {            if(mRefreshListener != null) {                mRefreshObject = mRefreshListener.refreshing();            }            Message msg = mHandler.obtainMessage();            msg.what = REFRESH_DONE;            mHandler.sendMessage(msg);        };    }.start();    break;caseREFRESH_DONE:    ... ...    mPullRefreshState = NONE_PULL_REFRESH;    setSelection(1);    if(mRefreshListener != null) {        mRefreshListener.refreshed(mRefreshObject);    }    break; | 
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | publicxxx implementsRefreshListener{@Override    protectedvoidonCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        //类似如下        ((RefreshListView) listView).setOnRefreshListener(this);    }    @Override    publicObject refreshing() {        String result = null;        //result = FileUtils.readTextFile(file);        returnresult;    }    @Override    publicvoidrefreshed(Object obj) {        if(obj != null) {           //扩展操作        }    };} | 
9. 扩展"更多"功能
    下拉刷新之外,我们也可以通过相同方法使用FooterView切入底部"更多"过程,这里我就不详细说明了
10. 源码
    上面的每段代码都看做是"零部件",需要组合一下。
    因为我们上面实现了下拉刷新,还增加了"更多"功能,我们直接命名这个类为RefreshListView吧:
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 | packagecom.tianxia.lib.baseworld.widget;importjava.text.SimpleDateFormat;importjava.util.Date;importandroid.content.Context;importandroid.os.Handler;importandroid.os.Message;importandroid.util.AttributeSet;importandroid.view.LayoutInflater;importandroid.view.MotionEvent;importandroid.view.View;importandroid.view.ViewGroup;importandroid.widget.AbsListView;importandroid.widget.AbsListView.OnScrollListener;importandroid.widget.ImageView;importandroid.widget.LinearLayout;importandroid.widget.ListAdapter;importandroid.widget.ListView;importandroid.widget.ProgressBar;importandroid.widget.TextView;importcom.tianxia.lib.baseworld.R;/** * 下拉刷新,底部更多 * */publicclassRefreshListView extendsListView implementsOnScrollListener{    privatefloatmDownY;    privatefloatmMoveY;    privateintmHeaderHeight;    privateintmCurrentScrollState;    privatefinalstaticintNONE_PULL_REFRESH = 0;    //正常状态    privatefinalstaticintENTER_PULL_REFRESH = 1;   //进入下拉刷新状态    privatefinalstaticintOVER_PULL_REFRESH = 2;    //进入松手刷新状态    privatefinalstaticintEXIT_PULL_REFRESH = 3;    //松手后反弹和加载状态    privateintmPullRefreshState = 0;                 //记录刷新状态    privatefinalstaticintREFRESH_BACKING = 0;      //反弹中    privatefinalstaticintREFRESH_BACED = 1;        //达到刷新界限,反弹结束后    privatefinalstaticintREFRESH_RETURN = 2;       //没有达到刷新界限,返回    privatefinalstaticintREFRESH_DONE = 3;         //加载数据结束    privateLinearLayout mHeaderLinearLayout = null;    privateLinearLayout mFooterLinearLayout = null;    privateTextView mHeaderTextView = null;    privateTextView mHeaderUpdateText = null;    privateImageView mHeaderPullDownImageView = null;    privateImageView mHeaderReleaseDownImageView = null;    privateProgressBar mHeaderProgressBar = null;    privateTextView mFooterTextView = null;    privateProgressBar mFooterProgressBar = null;    privateSimpleDateFormat mSimpleDateFormat;    privateObject mRefreshObject = null;    privateRefreshListener mRefreshListener = null;    publicvoidsetOnRefreshListener(RefreshListener refreshListener) {        this.mRefreshListener = refreshListener;    }    publicRefreshListView(Context context) {        this(context, null);    }    publicRefreshListView(Context context, AttributeSet attrs) {        super(context, attrs);        init(context);    }    voidinit(finalContext 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(newOnClickListener() {            @Override            publicvoidonClick(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 = newSimpleDateFormat("yyyy-MM-dd hh:mm");        mHeaderUpdateText.setText(context.getString(R.string.app_list_header_refresh_last_update, mSimpleDateFormat.format(newDate())));    }    @Override    publicbooleanonTouchEvent(MotionEvent ev) {        switch(ev.getAction()) {            caseMotionEvent.ACTION_DOWN:                mDownY = ev.getY();                break;            caseMotionEvent.ACTION_MOVE:                mMoveY = ev.getY();                if(mPullRefreshState == OVER_PULL_REFRESH) {                    mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),                            (int)((mMoveY - mDownY)/3),                            mHeaderLinearLayout.getPaddingRight(),                            mHeaderLinearLayout.getPaddingBottom());                }                break;            caseMotionEvent.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) {                    newThread() {                        publicvoidrun() {                            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;        }        returnsuper.onTouchEvent(ev);    }    @Override    publicvoidonScroll(AbsListView view, intfirstVisibleItem, intvisibleItemCount, inttotalItemCount) {        if(mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL                && firstVisibleItem == 0                && (mHeaderLinearLayout.getBottom() >= 0&& mHeaderLinearLayout.getBottom() < mHeaderHeight)) {            //进入且仅进入下拉刷新状态            if(mPullRefreshState == NONE_PULL_REFRESH) {                mPullRefreshState = ENTER_PULL_REFRESH;            }        } elseif(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);//显示向上的箭头            }        } elseif(mCurrentScrollState == SCROLL_STATE_TOUCH_SCROLL && firstVisibleItem != 0) {            //不刷新了            if(mPullRefreshState == ENTER_PULL_REFRESH) {                mPullRefreshState = NONE_PULL_REFRESH;            }        } elseif(mCurrentScrollState == SCROLL_STATE_FLING && firstVisibleItem == 0) {            //飞滑状态,不能显示出header,也不能影响正常的飞滑            //只在正常情况下才纠正位置            if(mPullRefreshState == NONE_PULL_REFRESH) {                setSelection(1);            }        }    }    @Override    publicvoidonScrollStateChanged(AbsListView view, intscrollState) {        mCurrentScrollState = scrollState;    }    @Override    publicvoidsetAdapter(ListAdapter adapter) {        super.setAdapter(adapter);        setSelection(1);    }    privatevoidmeasureView(View child) {        ViewGroup.LayoutParams p = child.getLayoutParams();        if(p == null) {            p = newViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,                    ViewGroup.LayoutParams.WRAP_CONTENT);        }        intchildWidthSpec = ViewGroup.getChildMeasureSpec(0, 0+ 0, p.width);        intlpHeight = p.height;        intchildHeightSpec;        if(lpHeight > 0) {            childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight,                    MeasureSpec.EXACTLY);        } else{            childHeightSpec = MeasureSpec.makeMeasureSpec(0,                    MeasureSpec.UNSPECIFIED);        }        child.measure(childWidthSpec, childHeightSpec);    }    privateHandler mHandler = newHandler(){        @Override        publicvoidhandleMessage(Message msg) {            switch(msg.what) {            caseREFRESH_BACKING:                mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),                        (int) (mHeaderLinearLayout.getPaddingTop()*0.75f),                        mHeaderLinearLayout.getPaddingRight(),                        mHeaderLinearLayout.getPaddingBottom());                break;            caseREFRESH_BACED:                mHeaderTextView.setText("正在加载...");                mHeaderProgressBar.setVisibility(View.VISIBLE);                mHeaderPullDownImageView.setVisibility(View.GONE);                mHeaderReleaseDownImageView.setVisibility(View.GONE);                mPullRefreshState = EXIT_PULL_REFRESH;                newThread() {                    publicvoidrun() {                        if(mRefreshListener != null) {                            mRefreshObject = mRefreshListener.refreshing();                        }                        Message msg = mHandler.obtainMessage();                        msg.what = REFRESH_DONE;                        mHandler.sendMessage(msg);                    };                }.start();                break;            caseREFRESH_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;            caseREFRESH_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(newDate())));                mHeaderLinearLayout.setPadding(mHeaderLinearLayout.getPaddingLeft(),                        0,                        mHeaderLinearLayout.getPaddingRight(),                        mHeaderLinearLayout.getPaddingBottom());                mPullRefreshState = NONE_PULL_REFRESH;                setSelection(1);                if(mRefreshListener != null) {                    mRefreshListener.refreshed(mRefreshObject);                }                break;            default:                break;            }        }    };    publicinterfaceRefreshListener {        Object refreshing();        voidrefreshed(Object obj);        voidmore();    }    publicvoidfinishFootView() {        mFooterProgressBar.setVisibility(View.GONE);        mFooterTextView.setText(R.string.app_list_footer_more);    }    publicvoidaddFootView() {        if(getFooterViewsCount() == 0) {            addFooterView(mFooterLinearLayout);        }    }    publicvoidremoveFootView() {        removeFooterView(mFooterLinearLayout);    }} | 
    这个只是一个原型,无论代码风格和逻辑处理
    以下是例子效果
    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
    期待有建设性的意见改善这个实现。
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号