首先非常感谢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()。
小编技术有限,希望各位大神多多指点不足之处。