我们知道页面的下拉刷新目前基本已经成为智能移动终端的标配刷新方式。Twitter设计出现有的下拉刷新(在申请了专利)。
下拉刷新1.jpg
这一优美而又简单的刷新方式,很快使得各大系统纷纷效仿;IOS在6.0开始引入,Android由于一开始不太重视交互体验这方面,直到Android5.0开始才借鉴这样一种刷新方式。那么我们今天就来研究研究怎么实现。
目前我收集到的不同方式有以下几种:
方案一: listview headview调用setPadding的方式
方案二: listview的多种样式显示 getview 实现
方案三: SwipeRefreshLayout实现下来刷新 没有加载更多
方案四:使用PullToRefresh 实现上拉加载和下拉刷新
方案五:使用Ultra-Pull-To-Refresh实现上拉加载和下拉刷新 类似方案三没有加载更多
我们首先讲解第一种实现方式,也是我们项目当中经常用到的。
实现思路:
1、首先为ListView添加头布局和底布局,addHeadView()
2、通过改变HeaderView的paddingTop值,来控制控件的显示和隐藏
3、根据我们滑动的状态,动态修改头部布局。
listview.png
完整的刷新过程:
1、下拉刷新——2、释放刷新——3、刷新中——1、下拉刷新
也就是分3种状态,那么分别什么时候切换状态呢。看以下具体分析:
listview状态图.png
1、下拉刷新——释放刷新:
当headView的paddingTop的值从-headViewHeight减小到0时,说明此刻为释放刷新;
2、释放刷新——刷新中:
headView的paddingTop一直大于0时,此时松开手指,将headView固定在paddingTop为0的位置,此时也是刚好请求网络的时机。
3、刷新中——下拉刷新:
刷新结束,paddingTop值由0重新回到-headViewHeight
代码实现逻辑:
1、实现 OnScrollListener 接口,在 onScroll() 方法中记录当前是否到顶部或者底部;
2、重写 onTouchEvent() 方法,通过moveY - downY得到偏移量,将偏移量设置为HeadView的paddingTop,使HeadView跟随手指移动产生变化;
3、onScrollStateChanged() 方法中根据状态变化(开始滚动或停止滚动),判断是否需要加载更多。
4、定义了回调接口,让用户自己编写下拉刷新的业务代码;
完整代码:
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE
|| scrollState == OnScrollListener.SCROLL_STATE_FLING) {
if (isScroll2Bottom && !isLoadMoving) { // 滚动到底部
// 加载更多
if (!canLoadMore) {
return;
}
footerView.setPadding(0, 0, 0, 0);
this.setSelection(this.getCount()); // 滚动到ListView的底部
isLoadMoving = true;
//回调接口执行请求加载
if (mOnRefreshListener != null) {
mOnRefreshListener.onLoadingMore();
}
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
mFirstVisibleItem = firstVisibleItem;
isScroll2Bottom = (firstVisibleItem + visibleItemCount) >= totalItemCount
&& totalItemCount > 0;
}
public boolean onTouchEvent(MotionEvent ev) {
if (isEnd) {//如果现在时结束的状态,即刷新完毕了,可以再次刷新了,在onRefreshComplete中设置
if (isRefreable) {//如果现在是可刷新状态 在setOnMeiTuanListener中设置为true
switch (ev.getAction()) {
//用户按下
case MotionEvent.ACTION_DOWN:
//如果当前是在listview顶部并且没有记录y坐标
if (mFirstVisibleItem == 0 && !isRecord) {
//将isRecord置为true,说明现在已记录y坐标
isRecord = true;
//将当前y坐标赋值给startY起始y坐标
startY = ev.getY();
}
break;
//用户滑动
case MotionEvent.ACTION_MOVE:
//再次得到y坐标,用来和startY相减来计算offsetY位移值
float tempY = ev.getY();
//再起判断一下是否为listview顶部并且没有记录y坐标
if (mFirstVisibleItem == 0 && !isRecord) {
isRecord = true;
startY = tempY;
}
//如果当前状态不是正在刷新的状态,并且已经记录了y坐标
if (state != REFRESHING && isRecord) {
//计算y的偏移量
offsetY = tempY - startY;
//如果当前的状态是放开刷新,并且已经记录y坐标
if (state == RELEASE_TO_REFRESH && isRecord) {
setSelection(0);
//如果当前滑动的距离小于headerView的总高度
if (-mHeaderViewHeight + offsetY / RATIO < 0) {
//将状态置为下拉刷新状态
state = PULL_TO_REFRESH;
//根据状态改变headerView,主要是更新动画和文字等信息
changeHeaderByState(state);
//如果当前y的位移值小于0,即为headerView隐藏了
} else if (offsetY <= 0) {
//将状态变为done
state = DONE;
//根据状态改变headerView,主要是更新动画和文字等信息
changeHeaderByState(state);
}
}
//如果当前状态为下拉刷新并且已经记录y坐标
if (state == PULL_TO_REFRESH && isRecord) {
setSelection(0);
//如果下拉距离大于等于headerView的总高度
if (-mHeaderViewHeight + offsetY / RATIO >= 0) {
//将状态变为放开刷新
state = RELEASE_TO_REFRESH;
//根据状态改变headerView,主要是更新动画和文字等信息
changeHeaderByState(state);
//如果当前y的位移值小于0,即为headerView隐藏了
} else if (offsetY <= 0) {
//将状态变为done
state = DONE;
//根据状态改变headerView,主要是更新动画和文字等信息
changeHeaderByState(state);
}
}
//如果当前状态为done并且已经记录y坐标
if (state == DONE && isRecord) {
//如果位移值大于0
if (offsetY >= 0) {
//将状态改为下拉刷新状态
state = PULL_TO_REFRESH;
}
}
//如果为下拉刷新状态
if (state == PULL_TO_REFRESH) {
//则改变headerView的padding来实现下拉的效果
headerView.setPadding(0, (int) (-mHeaderViewHeight + offsetY / RATIO), 0, 0);
}
//如果为放开刷新状态
if (state == RELEASE_TO_REFRESH) {
//改变headerView的padding值
headerView.setPadding(0, (int) (-mHeaderViewHeight + offsetY / RATIO), 0, 0);
}
}
break;
//当用户手指抬起时
case MotionEvent.ACTION_UP:
//如果当前状态为下拉刷新状态
if (state == PULL_TO_REFRESH) {
//平滑的隐藏headerView
headerView.setPadding(0, -mHeaderViewHeight, 0, 0);
//根据状态改变headerView
changeHeaderByState(state);
}
//如果当前状态为放开刷新
if (state == RELEASE_TO_REFRESH) {
//平滑的滑到正好显示headerView
headerView.setPadding(0, -mHeaderViewHeight, 0, 0);
//将当前状态设置为正在刷新
state = REFRESHING;
//回调接口的onRefresh方法
if (mOnRefreshListener != null) {
mOnRefreshListener.onRefresh();
}
//根据状态改变headerView
changeHeaderByState(state);
}
//这一套手势执行完,一定别忘了将记录y坐标的isRecord改为false,以便于下一次手势的执行
isRecord = false;
break;
}
}
}
return super.onTouchEvent(ev);
}
那么到此,我们就把下拉刷新分析完了。