手把手教你轻松实现listview下拉刷新

时间:2023-01-23 07:54:06

很多人觉得自定义一个listview下拉刷新上拉加载更多是一件很牛x的事情,不是大神写不出来,我想大多数童鞋都是做项目用到时就百度,什么pulltorefresh,xlistview。。。也不看原理,稍微改造下就用到项目中了,只管用,等再写新项目时又重复上面的步骤,那么今天,我来告诉大家,实现listview下拉刷新是多么简单的一件事情!

先来看下效果图:
手把手教你轻松实现listview下拉刷新手把手教你轻松实现listview下拉刷新手把手教你轻松实现listview下拉刷新手把手教你轻松实现listview下拉刷新

首先,我们来确定下下拉刷新的几个状态,如上图:
1.PULL_TO_REFRESH:下拉中(用户刚开始下拉,但headview未全部露出,也就是未达到可刷新状态),文字状态表现>>下拉刷新;
2.RELEASE_TO_REFRESH:准备刷新(只要用户未松手,其实这个状态也属于是下拉中的状态,只是此时已经达到了可刷新的状态,只要用户一松手,就将执行刷新操作),文字状态表现>>松开刷新;
3.REFRESHING:刷新中(执行刷新操作),文字状态表现>>正在刷新;
4.REFRESH_DONE:刷新完成(这个属于初始状态,以及刷新完成后的状态,所以控件及效果都要复原),文字状态表现>>下拉刷新(还原状态);

接下来,我们再看下满足下拉刷新的一些条件及原理:
1.addHeaderView:定义一个headview的layout,并将这个布局添加为headview,当然如果只执行addHeaderView是不行的,因为此时的headview是呈现在大家眼前的,因为它也是listview的一个item,那么我们可以通过设置headview的paddingTop来先把自己给隐藏起来,我们在编辑xml时即可看到paddingTop为正数时以及为负数时的区别,那么此时我们需要将paddingTop设置为-headview的高度,即可将其隐藏起来:

headerViewHeight = headerView.getMeasuredHeight();
        headerView.setPadding(0, -headerViewHeight, 0, 0);

2.onTouch:实现下拉刷新必不可少的部分,就是监听onTouch事件,在ACTION_MOVE里根据手指滑动的距离来判断是否达到或未达到下拉刷新的状态,以及根据手指滑动的距离来动态的设置headview的paddingTop以实现headview的高度变化(露出隐藏);

那么如何计算手指滑动的距离呢,很简单,监听ACTION_DOWN记录手指按下时y坐标:

startY = ev.getY();

在ACTION_MOVE里根据ev.getY()-startY 来计算手指移动的距离,首先我们需要了解,屏幕的坐标是从左顶点开始,x轴坐标从左往右逐渐增大,y轴坐标从上往下逐渐增大,那么我们下拉时,y轴的坐标是逐渐增大的,那么ev.getY()-startY必>0,这也将是在ACTION_MOVE里满足可执行下拉刷新操作的一个重要条件;
我们接着再看其它必备条件也就是达到什么条件,才去执行ACTION_MOVE里下拉部分的代码;我们可以想象下,如果listview上滑了很多行,我们再往下滑动时一样满足了ev.getY()-startY>0,但此时并不需要执行下拉刷新,那么我们就需要加一个条件是此时的listview已经滑动到了顶部也就是firstVisibleItem == 0,如何判断呢,监听onScroll即可:

 setOnScrollListener(this);

public void onScroll(AbsListView absListView, int firstVisibleItem, int visibleItemCount, int totalItemCount)

另外,如果listview已经在刷新状态REFRESHING,我们再下拉时也是不需要再执行的,那么连起来的这几个条件就是:

if (offsetY > 0 && isScrollFirst && refreshstate != REFRESHING)

好了,满足了这些条件后,我们又将怎么做呢?我们先定义一个刷新状态变量refreshstate,初始时refreshstate=REFRESH_DONE(不解释),那么我们在满足条件后,首先判断:

    if(refreshstate==REFRESH_DONE){ refreshstate = PULL_TO_REFRESH; }

这个容易理解吧,满足条件后,即立马将refreshstate 置为PULL_TO_REFRESH状态,接着判断:

if(refreshstate==PULL_TO_REFRESH){
                        setSelection(0);
                        if (headerViewShowHeight - headerViewHeight >= 0) {
                            refreshstate = RELEASE_TO_REFRESH;
                            changeHeaderByState(refreshstate);
                        }
                    }

setSelection(0)可以防止你在连续上拉下拉时headview的位置变化始终跟随手指,不加的话会出现位移偏差,headerViewShowHeight 即为headview露出部分的高度headerViewShowHeight =offsetY =ev.getY()-startY;

if (headerViewShowHeight - headerViewHeight >= 0)表示手指滑动的距离也就是headview露出的高度>headview的高度时,即达到了可执行刷新操作的状态refreshstate = RELEASE_TO_REFRESH;

changeHeaderByState(refreshstate)方法是根据刷新中的各种状态来改变布局里面的文字显示,动画效果,以及还原各控件状态等:

/** * 改变headview状态 * * @param state */
    private void changeHeaderByState(int state) {
        switch (state) {
            case REFRESH_DONE:
                headerView.setPadding(0, -headerViewHeight, 0, 0);
                tv_refresh.setText("下拉刷新");
                break;
            case RELEASE_TO_REFRESH:
                tv_refresh.setText("松开刷新");
                break;
            case PULL_TO_REFRESH:
                tv_refresh.setText("下拉刷新");
                break;
            case REFRESHING:
                headerView.setPadding(0, 0, 0, 0);
                tv_refresh.setText("正在刷新");
                break;
            default:
                break;
        }
    }

再接着判断:

if(refreshstate==RELEASE_TO_REFRESH){
                        setSelection(0);
                        if (headerViewShowHeight - headerViewHeight < 0) {
                            refreshstate = PULL_TO_REFRESH;
                            changeHeaderByState(refreshstate);
                        }
                    }

当此时为RELEASE_TO_REFRESH时,已经可以执行刷新操作了,但是用户此时并未松手释放,又慢慢向上滑了回去,以至于刚headerViewShowHeight - headerViewHeight >= 0又变为了headerViewShowHeight - headerViewHeight<0,那么此时刷新状态就又回到了PULL_TO_REFRESH;

然而除了以上一系列判断,还有最重要的一步,就是动态改变headview的paddingTop啊,要不然headview跟随手指下拉上滑的露出隐藏效果怎么实现啊?

    if (refreshstate == PULL_TO_REFRESH || refreshstate == RELEASE_TO_REFRESH) {
                        headerView.setPadding(0, (int) (headerViewShowHeight - headerViewHeight), 0, 0);
                    }

好了,手指按下及滑动都处理了,那么还差什么呢,就是手指抬起(ACTION_UP)时的监听:

if (refreshstate == PULL_TO_REFRESH) { refreshstate = REFRESH_DONE; changeHeaderByState(refreshstate); }
                if (refreshstate == RELEASE_TO_REFRESH) { refreshstate = REFRESHING; changeHeaderByState(refreshstate); mOnRefreshListener.onRefresh(); }

抬起时,如果状态仍为PULL_TO_REFRESH,那么没达到刷新条件,即将所有控件复原,如果状态为RELEASE_TO_REFRESH,即达到了刷新条件,则将状态refreshstate 置为REFRESHING后,更新headview,并调用刷新接口mOnRefreshListener.onRefresh()(自定义的刷新回调接口);

那么整个onTouch事件:

 @Override
    public boolean onTouchEvent(MotionEvent ev) {
        switch (ev.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startY = ev.getY();
                break;
            case MotionEvent.ACTION_MOVE:
                offsetY = ev.getY() - startY;
                if (offsetY > 0 && isScrollFirst && refreshstate != REFRESHING) {
                    float headerViewShowHeight = offsetY/REFRESH_RATIO;
                    switch (refreshstate) {
                        case REFRESH_DONE:
                            refreshstate = PULL_TO_REFRESH;
                            break;
                        case PULL_TO_REFRESH:
                            setSelection(0);
                            if (headerViewShowHeight - headerViewHeight >= 0) {
                                refreshstate = RELEASE_TO_REFRESH;
                                changeHeaderByState(refreshstate);
                            }
                            break;
                        case RELEASE_TO_REFRESH:
                            setSelection(0);
                            if (headerViewShowHeight - headerViewHeight < 0) {
                                refreshstate = PULL_TO_REFRESH;
                                changeHeaderByState(refreshstate);
                            }
                            break;
                    }

                    if (refreshstate == PULL_TO_REFRESH || refreshstate == RELEASE_TO_REFRESH) {
                        headerView.setPadding(0, (int) (headerViewShowHeight - headerViewHeight), 0, 0);
                    }
                }
                break;
            case MotionEvent.ACTION_UP:
                if (refreshstate == PULL_TO_REFRESH) {
                    refreshstate = REFRESH_DONE;
                    changeHeaderByState(refreshstate);
                }
                if (refreshstate == RELEASE_TO_REFRESH) {
                    refreshstate = REFRESHING;
                    changeHeaderByState(refreshstate);
                    mOnRefreshListener.onRefresh();
                }
                break;
        }
        return super.onTouchEvent(ev);
    }

到这里,其实就已经实现了下拉刷新的功能了,是不是很简单,但如果你只用以上代码的话,可能会有点小问题,就是下拉灵敏度太高:
手把手教你轻松实现listview下拉刷新
这体验效果也不太好吧,哈哈!解决这个问题也很简单,比如我们想要手指移动高度和headview变化高度为3:1,即我手指滑动3个像素,headview才露出一个像素,那么我们可以声明一个系数变量:

private static final float REFRESH_RATIO = 3.0f;//下拉系数,越大下拉灵敏度越低

offsetY = ev.getY() - startY;

headerViewShowHeight = offsetY/REFRESH_RATIO ;

后面动态改变headview高度的代码:

headerView.setPadding(0, (int) (headerViewShowHeight - headerViewHeight), 0, 0);

也就相应缩小了。

这个例子比较简单,headview中只有文字变化,像我们常用的那种有箭头的,有progress的,甚至有动画放大缩小的,只要掌握了这几种状态变化,就很容易实现了!

另外:因为headview也属于listview的一个item,因此在做item点击事件时一定要排除headview的点击事件,也就是当position==0时不要做任何处理!

看完了这篇文章,你是不是有所启发呢,如果有,我写这篇文章的目的就达成了,如果没有,就多看几遍直到理解为止!

demo下载地址:http://download.csdn.net/detail/baiyuliang2013/9343383