@SuppressLint({ "InflateParams", "SimpleDateFormat" })
public class RefreshListView extends ListView implements OnScrollListener {
private View header;// 要显示的顶部布局文件
private int headerHeight;// 顶部布局文件的高度
private int firstVisibleItem;// 当前第一个可见的item,下表从0开始
private boolean isRemarkFirstItem;// 按下的是否是第一个item,即如果不是第一个item的话就不用出现header
private int startYLocation;// 开始的位置
private int scrollState; // 用于保存list的当前滚动状态
private int moveStatement;// 不同的下拉状态
private static final int NORMOL = 0;// 正常的header是原样式的位置状态
private static final int PULL = 1;// header 变更为 提示下拉的状态
private static final int RELEASE = 2;// header 变更为 提示释放刷新的状态
private static final int RELEASING = 3;// header 变更为 提示正在刷新的状态
/*
* 重写构造方法
*/
public RefreshListView(Context context) {
super(context);
initListView(context);
// TODO Auto-generated constructor stub
}
public RefreshListView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initListView(context);
// TODO Auto-generated constructor stub
}
public RefreshListView(Context context, AttributeSet attrs) {
super(context, attrs);
initListView(context);
// TODO Auto-generated constructor stub
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
// 正在滚动时回调,回调2-3次,手指没抛则回调2次。scrollState = 2的这次不回调
// 回调顺序如下
// 第1次:scrollState = SCROLL_STATE_TOUCH_SCROLL(值为1) 正在滚动
// 第2次:scrollState = SCROLL_STATE_FLING(值为2) 手指做了抛的动作(手指离开屏幕前,用力滑了一下)
// 第3次:scrollState = SCROLL_STATE_IDLE(值为0) 停止滚动
// 当屏幕停止滚动时为0;当屏幕滚动且用户使用的触碰或手指还在屏幕上时为1;
// 由于用户的操作,屏幕产生惯性滑动时为2
this.scrollState = scrollState;// 将当前的list的滚动状态保存下来
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
// firstVisibleItem:当前能看见的第一个列表项ID(从0开始)
// visibleItemCount:当前能看见的列表项个数(小半个也算)
// totalItemCount:列表项共数
this.firstVisibleItem = firstVisibleItem;
}
@SuppressLint("ClickableViewAccessibility")
@Override
public boolean onTouchEvent(MotionEvent ev) {
// TODO Auto-generated method stub
switch (ev.getAction()) {
case MotionEvent.ACTION_DOWN:
if (firstVisibleItem == 0) {
isRemarkFirstItem = true;
startYLocation = (int) ev.getY();// 获取触摸开始的位置
}
refreshHeaderByMoveStatement();// 刷新header界面显示
break;
case MotionEvent.ACTION_HOVER_MOVE:// 移动
onMove(ev);
refreshHeaderByMoveStatement();// 刷新header界面显示
break;
case MotionEvent.ACTION_UP:// 松开
if (moveStatement == RELEASE) {// 如果是抬起的当前位置是提示刷新的状态
moveStatement = RELEASING;// 松开后,状态就变为正在刷新的状态
/*
* 在这里就要另开线程,从服务器上次指定的位置加载数据***********************************
* 并且将header的样式改变
*/
refreshComplete();
} else if (moveStatement == PULL) {
moveStatement = NORMOL;
isRemarkFirstItem = false;
}
refreshHeaderByMoveStatement();// 刷新header界面显示
break;
}
return super.onTouchEvent(ev);
}
/**
* 判断移动的过程,根据到达的不同位置,将header设置为不同的状态 (前提是:列表已经下拉到显示的第一个item是0,即到达list的头位置)
* 有个问题这里只用了设置的三个状态
*/
private void onMove(MotionEvent ev) {
// TODO Auto-generated method stub
if (!isRemarkFirstItem) {// 如果是没达到列表的头位置,就不对header做任何处理
return;
}
int currentYLocation = (int) ev.getY();// 当前移动到的位置
int tempY = currentYLocation - startYLocation;// 移动的距离
int topPadding = tempY - headerHeight;// header的上边距
switch (moveStatement) {
case NORMOL:
if (tempY > 0) // 如果下拉的位移变化小于header的高度,就显示的是原来的样式
moveStatement = PULL;// 下拉状态
break;
case PULL:
setTopPadding(topPadding);// 当处于下拉状态时,设置header的上边距(即onmove在回调时体现的是不断向下状态)
if (tempY > headerHeight + 30 && scrollState == SCROLL_STATE_TOUCH_SCROLL) {
moveStatement = RELEASE;// 刷新状态
}
break;
case RELEASE:
setTopPadding(topPadding);// 同上
if (tempY < headerHeight + 30) {
moveStatement = PULL;// 下拉状态
} else if (tempY < 0) {
moveStatement = NORMOL;
isRemarkFirstItem = false;
}
break;
case RELEASING:
break;
default:
break;
}
}
/**
* @param refreshHeaderByMoveStatement
* 根据当前的状态来改变header的样式
*/
private void refreshHeaderByMoveStatement() {
TextView refreshStatement = (TextView) header.findViewById(R.id.refreshstatement);
ImageView statementImage = (ImageView) header.findViewById(R.id.statementimage);
ProgressBar refreshProgress = (ProgressBar) header.findViewById(R.id.refreshprogress);
/*
* 一下这一段代码是添加一个切换图片的动画效果
* fillBefore是指动画结束时画面停留在第一帧,fillAfter是指动画结束是画面停留在最后一帧。
*/
RotateAnimation anim = new RotateAnimation(0, 180, RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
anim.setDuration(500);
anim.setFillAfter(true);
RotateAnimation anim1 = new RotateAnimation(180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
anim1.setDuration(500);
anim1.setFillAfter(true);
switch (moveStatement) {
case NORMOL:
// 正常状态不显示
setTopPadding(-headerHeight);
statementImage.clearAnimation();// 移除动画效果
break;
case PULL:
statementImage.setVisibility(View.VISIBLE);// 图片显示显示
refreshProgress.setVisibility(View.GONE);// 进度条不显示
refreshStatement.setText("下拉刷新");
statementImage.clearAnimation();// 移除动画效果
statementImage.setAnimation(anim1);
break;
case RELEASE:
statementImage.setVisibility(View.VISIBLE);// 图片显示显示
refreshProgress.setVisibility(View.GONE);// 进度条不显示
// statementImage.setBackgroundResource(R.drawable.release_to_refresh_arrow);//
// 将图片设为向上的箭头
refreshStatement.setText("松开刷新");
statementImage.clearAnimation();// 移除动画效果
statementImage.setAnimation(anim);
break;
case RELEASING:
setTopPadding(headerHeight);
statementImage.clearAnimation();// 移除动画效果
statementImage.setVisibility(View.GONE);// 图片显示显示
refreshProgress.setVisibility(View.VISIBLE);// 进度条不显示
refreshStatement.setText("正在刷新...");
break;
default:
break;
}
}
/**
*获取到数据之后
*/
private void refreshComplete() {
moveStatement = NORMOL;
isRemarkFirstItem = false;
refreshHeaderByMoveStatement();
TextView lastRefreshTime = (TextView) header.findViewById(R.id.lastrefreshtime);
SimpleDateFormat date = new SimpleDateFormat("yyyy年MM月dd日 hh:mm:ss");//设置时间格式
Date now = new Date(System.currentTimeMillis());//系统当前时间
String showDate = date.format(now);
lastRefreshTime.setText(showDate);
}
@SuppressLint("SimpleDateFormat")
private void initListView(Context context) {
// 初始化方法,添加顶部布局文件到listview
LayoutInflater inflater = LayoutInflater.from(context);
header = inflater.inflate(R.layout.oninelist, null);
this.addHeaderView(header);// 为列表添加顶部view
measureView(header);
headerHeight = header.getMeasuredHeight();// 获取顶部布局的高度的方法
Log.i("tag", "TopPadding " + headerHeight);
setTopPadding(-headerHeight);
this.setOnScrollListener(this);// 设置滚动监听
}
/**
* 设置header布局的上边距,此方法的前提是父布局已知顶部布局所占的的空间大小,所以 要设置一个方法告诉父布局到底占了多大位置
*
* @param topPadding
*/
private void setTopPadding(int topPadding) {
header.setPadding(header.getPaddingLeft(), topPadding, header.getPaddingRight(), header.getPaddingBottom());
}
/**
* 通知父布局占用的宽, 高
*
* @param view
*/
private void measureView(View view) {
ViewGroup.LayoutParams p = view.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
int width = ViewGroup.getChildMeasureSpec(0, 0, p.width);// 获取子布局的宽度,方法的前两个参数是控件的左右边距,第二个参数
// 是内边距
int height;
int tempHeight = p.height;
if (tempHeight > 0) {
height = MeasureSpec.makeMeasureSpec(tempHeight, MeasureSpec.EXACTLY);
} else {
height = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
view.measure(width, height);
}
}