首先非常感谢markmjw在Github上面给我们带来非常好用的下拉刷新控件XListView以及XScrollView。
Github地址是:https://github.com/Maxwin-z/XListView-Android 有需要的可以自行下载。

先简单介绍一下下拉刷新这个功能,相信大家在日常APP使用过程中已经接触过非常多的下拉刷新。下拉刷新主要有两种样式,一种是下拉时整个UI布局都往下拉,露出顶端的Loading,刷新成功之后再弹回去,还有一种是下拉后整个UI布局不变,拉出一个Loading在布局之上。今天介绍的属于前者。
XListView实现的功能有下拉刷新,上拉加载更多,当然也可以进入页面的时候自动刷新(与手动操作的动画相同),也可自动加载更多(滚动到底部的时候自动加载更多)。
首先看一下XListViewHeader的整体架构布局,在最顶部自定义一个,作为ListView的HeaderView。因为XListView是继承ListView,所以主体就是ListView。在最底部自定义了XFooterView,作为ListView的FooterView。整体框架就是XHeaderView用来显示下拉刷新时展现的动画,XFooterView用来显示加载更多的动画或文字,中间即为ListView主体。查看源码,可以在initWithContext()方法中看到HeaderView和FooterView的初始化。以及定义了HeaderView的默认高度,这个对之后的下拉刷新动画实现有着至关重要的作用。

private void initWithContext(Context context) {
        mScroller = new Scroller(context, new DecelerateInterpolator());
        // XListView need the scroll event, and it will dispatch the event to
        // user's listener (as a proxy).
        super.setOnScrollListener(this);

        // init header view
        mHeaderView = new XListViewHeader(context);
        mHeaderViewContent = (RelativeLayout) mHeaderView
                .findViewById(R.id.xlistview_header_content);
        mHeaderTimeView = (TextView) mHeaderView
                .findViewById(R.id.xlistview_header_time);
        addHeaderView(mHeaderView);

        // init footer view
        mFooterView = new XListViewFooter(context);

        // init header height
        mHeaderView.getViewTreeObserver().addOnGlobalLayoutListener(
                new OnGlobalLayoutListener() {
                    @Override
                    public void onGlobalLayout() {
                        mHeaderViewHeight = mHeaderViewContent.getHeight();
                        getViewTreeObserver()
                                .removeGlobalOnLayoutListener(this);
                    }
                });
    }

XListView来实现下拉刷新最重要的一点就是,当XListView滚动到顶端的时候,识别用户手势下拉距离超过一定距离(一般是超过XListViewHeader的默认高度mHeaderViewHeight)这个时候用户松开,当这个时候就去执行刷新任务。如果在下拉距离不到一定距离时,取消这次刷新操作。
加载更多与下列刷新很类似,当XListView滚动到底部时,用户继续往上拉,距离超过规定距离时,如果这个时候松手即去回调加载任务。
这些个事件监听都是在onTouchEvent()这个方法里面实现。

@Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (mLastY == -1) {
            mLastY = ev.getRawY();//手指移动到最后的Y坐标 
        }

        switch (ev.getAction()) {
        case MotionEvent.ACTION_DOWN://手指按下的手势
            mLastY = ev.getRawY();
            break;
        case MotionEvent.ACTION_MOVE://手指移动事件
            final float deltaY = ev.getRawY() - mLastY;//与上一点在Y轴上移动的距离
            mLastY = ev.getRawY();
            if (getFirstVisiblePosition() == 0
                    && (mHeaderView.getVisiableHeight() > 0 || deltaY > 0)) {
                //这个if是用来判断当前ListView是否滚动到顶端,HeaderView显示出的部分高度已经大于0或者
                //deltaY > 0,即用户往下拉,这个时候需要更新HeaderView的高度deltaY / OFFSET_RADIO,
                //OFFSET_RADIO为阻尼系数,如果想HeaderView显露出来慢些,可以将这个值设置大一点
                // the first item is showing, header has shown or pull down.
                updateHeaderHeight(deltaY / OFFSET_RADIO);
                invokeOnScrolling();
            } else if (getLastVisiblePosition() == mTotalItemCount - 1
                    && (mFooterView.getBottomMargin() > 0 || deltaY < 0)) {
                //这里是判断 ListView已经滚动到底部,FooterView已经脱离底部或者deltaY < 0用户上拉
                //这个时候就需要更新FooterView的高度了,与上面类似。
                // last item, already pulled up or want to pull up.
                updateFooterHeight(-deltaY / OFFSET_RADIO);
            }
            break;
        default:
            mLastY = -1; // reset 此时手指松开,初始化屏幕点击处Y坐标为-1
            if (getFirstVisiblePosition() == 0) {//ListView滚动至顶端
                // invoke refresh
                if (mEnablePullRefresh
                        && mHeaderView.getVisiableHeight() > mHeaderViewHeight) {
                    //可以刷新,HeaderView显示高度已超过阈值,即可以刷新
                    //将HeaderView状态置为刷新中,回调刷新方法
                    mPullRefreshing = true;
                    mHeaderView.setState(XListViewHeader.STATE_REFRESHING);
                    if (mListViewListener != null) {
                        mListViewListener.onRefresh();
                    }
                }
                //将HeaderView高度返回到默认高度
                resetHeaderHeight();
            } else if (getLastVisiblePosition() == mTotalItemCount - 1) {
                //ListView滚动到了底部
                // invoke load more.
                if (mEnablePullLoad
                    && mFooterView.getBottomMargin() > PULL_LOAD_MORE_DELTA
                    && !mPullLoading) {
                    //可以加载更多,FooterView与底部距离已超过阈值,
                    //且不是正在加载中(加上这个判断主要是放置重复加载)
                    //开始加载更多
                    startLoadMore();
                }
                //将FooterView滚动到最底部
                resetFooterHeight();
            }
            break;
        }
        return super.onTouchEvent(ev);
    }

具体这个方法怎么做的看代码中注释。
当然刷新什么内容,怎么刷新,加载什么内容,怎么加载都由回调完成。又一下接口及方法来完成。

public interface IXListViewListener {
        public void onRefresh();

        public void onLoadMore();
    }
    public void setXListViewListener(IXListViewListener l) {
        mListViewListener = l;
    }

到现在为止,下拉刷新和加载更多主体功能就介绍完了。接下来再介绍一下一些配置和扩展功能。
配置包括是否可以使用下拉刷新功能,是否可以自动刷新,是否可以下拉加载更多,是否可以自动加载更多。参数和方法如下:

private boolean mEnablePullRefresh = true;//下拉刷新是默认开启的
private boolean mEnablePullLoad;//加载更多是默认关闭的
public void setPullRefreshEnable(boolean enable) {
        mEnablePullRefresh = enable;
        if (!mEnablePullRefresh) { // disable, hide the content
            //如果设置下拉刷新不可用,就把HeaderView隐藏
            mHeaderViewContent.setVisibility(View.INVISIBLE);
        } else {
            mHeaderViewContent.setVisibility(View.VISIBLE);
        }
    }
public void setPullLoadEnable(boolean enable) {
        mEnablePullLoad = enable;
        if (!mEnablePullLoad) {
            //将上拉加载更多设为不可用,将FooterView隐藏,
            mFooterView.hide();
            mFooterView.setOnClickListener(null);
            //make sure "pull up" don't show a line in bottom when listview with one page 
            setFooterDividersEnabled(false);
        } else {
            //将上拉加载更多设为可用,将FooterView显示,
            //且还需要为FooterView添加一个点击事件,点击“加载更多”也可开始加载
            mPullLoading = false;
            mFooterView.show();
            mFooterView.setState(XListViewFooter.STATE_NORMAL);
            //make sure "pull up" don't show a line in bottom when listview with one page  
            setFooterDividersEnabled(true);
            // both "pull up" and "click" will invoke load more.
            mFooterView.setOnClickListener(new OnClickListener() {
                @Override
                public void onClick(View v) {
                    startLoadMore();
                }
            });
        }
    }

最新版貌似把自动刷新去掉了,但是可以自己加入。注意autoRefresh()这个方法必须放在onWindosFocusChanged()中才有效。

public void autoRefresh() {
        mHeader.setVisibleHeight(mHeaderHeight);

        if (mEnablePullRefresh && !mPullRefreshing) {
            // update the arrow image not refreshing
            if (mHeader.getVisibleHeight() > mHeaderHeight) {
                mHeader.setState(XHeaderView.STATE_READY);
            } else {
                mHeader.setState(XHeaderView.STATE_NORMAL);
            }
        }

        mPullRefreshing = true;
        mHeader.setState(XHeaderView.STATE_REFRESHING);
        if (mEnablePullRefresh && null != mListener) {
            mListener.onRefresh();
        }
    }

滚动到底部自动加载更多这个功能是我自己加的,希望对大家有帮助。在onScrollStateChanged()方法中,当监听到滚动停止时,且已滚动到底部。这个时候就去加载更多。

@Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        if (mScrollListener != null) {
            mScrollListener.onScrollStateChanged(view, scrollState);
        }
        if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {//当滚动停止时
            if (mEnableAutoLoad && getLastVisiblePosition() == getCount() - 1) {
                startLoadMore();
            }
        }
    }

当然还有最后一步,当刷新完毕后不要忘记调用stopRefresh(),加载完毕后不要忘记stopLoadMore()。
小编技术有限,希望各位大神多多指点不足之处。