之前在用ListView做下拉刷新和上拉加载的时候一直都是在网上找的实例,
但效果都不是太好,然后自己根据实例的思路,自己梳理了一下,自己也写了一把,感觉也不是太好,在此做下记录吧,希望对初入ANDROID开发的新手们有所帮助,直接上代码吧
package cn.zan.control.view;
import java.util.Date;
import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import cn.zan.zan_society.R;
/**
* Gening 2013-10-31
**/
public class CustomListView extends ListView {
private static final int DONE = 0;
private static final int PULL_TO_REFRESH = 1;
private static final int RELEASE_TO_REFRESH = 2;
private static final int REFRESHING = 3;
private static final float RATIO = 3;
private int state; // 当前下拉刷新的状态
private int firstVisibleIndex; // ListView 中第一个可以看见的Item下标
/***************************** ListView 头部 View 控件 ***************************/
private View headView; // ListView 头部 View
private ImageView headArrow; // ListView 头部 View的箭头
private ProgressBar progressBar; // ListView 头部 View的读取转圈
private TextView headTitle; // ListView 头部 View里的文字
private TextView headLastUpdate; // ListView 头部 View里的文字,最后更新时间
private int headContentHeight; // ListView 头部 View的高度
private Animation animation;
private Animation reverseAnimation;
private boolean isRefreshable;
private boolean isRecored = false; // 用来记录第一次按下坐标点,在整个滑动的过程中 只记录一次
private float startY;
private boolean isBack = false;
private boolean isFootLoading = false; // 正在加载底部数据标识
private boolean hasFoot = false; // 是否有了底部 View(FootView)
private int lastPos; // 最后一个可见的item的位置
private int count; // ListView Item总数,注意不是当前可见的item总数
public CustomListView(Context context, AttributeSet attrs) {
super(context, attrs);
if (isInEditMode()) { return; }
init(context);
}
private void init(Context context) {
// listview 设置滑动时缓冲背景色
setCacheColorHint(0x00000000);
headView = View.inflate(context, R.layout.head, null);
headArrow = (ImageView) headView.findViewById(R.id.head_arrow);
progressBar = (ProgressBar) headView.findViewById(R.id.progressbar);
headTitle = (TextView) headView.findViewById(R.id.head_title);
headLastUpdate = (TextView) headView.findViewById(R.id.head_last_update);
headArrow.setMinimumWidth(50);
headArrow.setMinimumHeight(70);
MeasureView(headView);
headContentHeight = headView.getMeasuredHeight();
headView.setPadding(0, -1 * headContentHeight, 0, 0);
addHeaderView(headView); // 为 ListView加入顶部 View
this.setOnScrollListener(custom_listview_onscroll_lis);
animation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
animation.setDuration(250);
animation.setFillAfter(true); // 设定动画结束时,停留在动画结束位置 (保留动画效果)
animation.setInterpolator(new LinearInterpolator()); // 匀速变化
reverseAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
reverseAnimation.setDuration(200);
reverseAnimation.setFillAfter(true);// 设定动画结束时,停留在动画结束位置 (保留动画效果)
reverseAnimation.setInterpolator(new LinearInterpolator());// 匀速变化
// 设置当前headView的状态
state = DONE;
// 设置当前下拉刷新是否可用
isRefreshable = false;
}
/** 测量headView的 宽高 **/
private void MeasureView(View child) {
ViewGroup.LayoutParams lp = child.getLayoutParams();
if (null == lp) {
lp = new ViewGroup.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
}
int measureChildWidth = ViewGroup.getChildMeasureSpec(0, 0, lp.width);
int measureChildHeight;
if (lp.height > 0) {
measureChildHeight = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
} else {
measureChildHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
child.measure(measureChildWidth, measureChildHeight);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
if (isRefreshable) {
if (firstVisibleIndex == 0 && !isRecored) {
startY = event.getY();
isRecored = true;
}
}
break;
case MotionEvent.ACTION_MOVE:
if (isRefreshable) {
float tempY = event.getY();
if (firstVisibleIndex == 0 && !isRecored) {
startY = tempY;
isRecored = true;
}
if (isRecored) {
if (tempY - startY < 0) {
break;
}
if (state != REFRESHING) {
if (state == PULL_TO_REFRESH) {
if ((tempY - startY) / RATIO >= headContentHeight && (tempY - startY) > 0) {
// 向下拉了 从下拉刷新的状态 来到 松开刷新的状态
state = RELEASE_TO_REFRESH;
changeHeadViewOfState();
} else if ((tempY - startY) <= 0) {
// 向上推了 从下拉刷新的状态 来到 刷新完成的状态
state = DONE;
changeHeadViewOfState();
}
} else if (state == RELEASE_TO_REFRESH) {
// 向上推了 还没有完全将HEADVIEW 隐藏掉(可以看到一部分)
if ((tempY - startY) / RATIO < headContentHeight && (tempY - startY) > 0) {
// 从松开刷新的状态 来到 下拉刷新的状态
state = PULL_TO_REFRESH;
changeHeadViewOfState();
isBack = true;
} else if ((tempY - startY) <= 0) {
// 向上推了 一下子推到了最上面 从松开刷新的状态 来到 刷新完成的状态 (数据不刷新的)
state = DONE;
changeHeadViewOfState();
}
} else if (state == DONE) {
// 刷新完成的状态 来到 下拉刷新的状态
if ((tempY - startY) > 0) {
state = PULL_TO_REFRESH;
changeHeadViewOfState();
}
}
if (state == PULL_TO_REFRESH)
headView.setPadding(0, (int) ((tempY - startY) / RATIO - headContentHeight), 0, 0);
if (state == RELEASE_TO_REFRESH)
headView.setPadding(0, (int) ((tempY - startY) / RATIO - headContentHeight), 0, 0);
}
}
}
break;
case MotionEvent.ACTION_UP:
if (isRefreshable) {
if (state != REFRESHING) {
if (state == PULL_TO_REFRESH) {
// 松手
state = DONE;
changeHeadViewOfState();
} else if (state == RELEASE_TO_REFRESH) {
// 松手
state = REFRESHING;
changeHeadViewOfState();
// 执行数据刷新方法
onRefresh();
}
}
isRecored = false;
isBack = false;
}
break;
}
return super.onTouchEvent(event);
}
/** 改变下拉刷新时,头部控件显示样式 **/
private void changeHeadViewOfState() {
switch (state) {
case PULL_TO_REFRESH:
headArrow.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
headTitle.setVisibility(View.VISIBLE);
headLastUpdate.setVisibility(View.VISIBLE);
headArrow.clearAnimation();
headTitle.setText("下拉可以刷新");
// 由 松开刷新 到 下拉刷新
if (isBack) {
headArrow.startAnimation(animation);
isBack = false;
}
break;
case RELEASE_TO_REFRESH:
headArrow.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
headTitle.setVisibility(View.VISIBLE);
headLastUpdate.setVisibility(View.VISIBLE);
headArrow.clearAnimation();
headArrow.startAnimation(reverseAnimation);
headTitle.setText("松开可以刷新");
break;
case REFRESHING:
headArrow.setVisibility(View.GONE);
progressBar.setVisibility(View.VISIBLE);
headTitle.setVisibility(View.VISIBLE);
headLastUpdate.setVisibility(View.VISIBLE);
headArrow.clearAnimation();
headTitle.setText("正在刷新...");
headView.setPadding(0, 0, 0, 0);
break;
case DONE:
headArrow.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
headTitle.setVisibility(View.VISIBLE);
headLastUpdate.setVisibility(View.VISIBLE);
headArrow.clearAnimation();
headTitle.setText("下拉可以刷新");
headView.setPadding(0, -1 * headContentHeight, 0, 0);
break;
}
}
/** ListView 监听事件 **/
private OnScrollListener custom_listview_onscroll_lis = new OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (!isFootLoading) {
// 没有滚动
if (hasFoot && scrollState == SCROLL_STATE_IDLE) {
isFootLoading = true;
onFootLoading();
}
}
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
firstVisibleIndex = firstVisibleItem;
lastPos = getLastVisiblePosition();
count = totalItemCount;
// 因为刚进入的时候,lastPos=-1,count=0,这个时候不能让它执行onAddFoot方法
if (lastPos == count - 1 && !hasFoot && lastPos != -1) {
hasFoot = true;
onAddFoot();
}
}
};
/** 下拉刷新监听 **/
private OnRefreshListner refreshListner; // 刷新监听器
/** 设置下拉刷新监听器 **/
public void setOnRefreshListner(OnRefreshListner listener) {
isRefreshable = true;
refreshListner = listener;
}
/** 执行下拉刷新操作 **/
private void onRefresh() {
if (refreshListner != null) {
refreshListner.onRefresh();
}
}
/** 添加底部View(FootView)监听器 **/
public OnAddFootListener onAddFootListener;
/** 设置添加Foot监听器 **/
public void setOnAddFootListener(OnAddFootListener addFootListener) {
onAddFootListener = addFootListener;
}
/** 执行添加Foot **/
public void onAddFoot() {
if (onAddFootListener != null && hasFoot) {
onAddFootListener.addFoot();
}
}
/** 上拉加载,监听器 **/
public OnFootLoadingListener footLoadingListener;
public void setOnFootLoadingListener(OnFootLoadingListener footLoading) {
footLoadingListener = footLoading;
}
/** 执行底部加载 **/
public void onFootLoading() {
if (footLoadingListener != null && isFootLoading) {
footLoadingListener.onFootLoading();
}
}
/********************************************************************** 监听器 **************************************************************************/
/** 下拉刷新监听器 **/
public interface OnRefreshListner {
// 下拉刷新的时候,在这里执行获取数据的过程
void onRefresh();
}
/** 添加Foot的监听器 **/
public interface OnAddFootListener {
// 这里是用户addFootView的操作
void addFoot();
}
/** 上拉加载监听器 **/
public interface OnFootLoadingListener {
// 上拉加载的时候,在这里行后台获取数据的过程
void onFootLoading();
}
/******************************************************************** 对外调用方法 *****************************************************************/
/** 底部数据加载完成,用户需要加入一个removeFootView的操作 **/
public void onFootLoadingComplete() {
hasFoot = false;
isFootLoading = false;
}
/** 上拉刷新完成时 所执行的操作,更改状态,隐藏head **/
public void onRefreshComplete() {
state = DONE;
changeHeadViewOfState();
headLastUpdate.setText("最后刷新时间: " + new Date().toLocaleString());
}
@Override
public void setAdapter(ListAdapter adapter) {
headLastUpdate.setText("最后刷新时间: " + new Date().toLocaleString());
super.setAdapter(adapter);
}
}
在外部使用的时候,
需使用下拉刷新,直接实现CustomListView的OnRefreshListenner接口
需使用上拉加载,先实现Custom的setOnAddFootListener接口为ListView赋上想要显示的布局,然后再实现OnFootLoadingListener接口执行加载时需要做的请求、数据解析及ListView数据源刷新。最后需调用CustomListView.OnFootLoadingComplete()回复初始状态 和 CustomListView.removeFooterView()移除FootViewW布局。
大概实现就是这样,在此因为时间的原因没有写上实现的思路,有时间补上。但是此实例有明显的不足之处。
1. 下拉刷新与上拉加载的效果太突兀,还有待优化。
2. 下拉刷新与上拉加载的touch事件实现上区分的还不是很清晰明确,还有待优化。