下拉刷新的ListView在很多App中都很常见,以下为我对实现方法的一些分析:
1. ListView需要有一个header,用来显示刷新提示。header中有一个指示箭头,一个使箭头旋转的动画,一个刷新时显示的progressbar,提示“下拉刷新/松开刷新”的TextView,提示上次刷新时间的TextView
2. header有4种状态:
DONE(header隐藏在顶部)
PULL_TO_REFRESH(header正在下拉,箭头向下,progressbar隐藏,提示“下拉刷新”)
RELEASE_TO_REFRESH(header正在下拉,箭头向上,progressbar隐藏,提示“松开刷新”)
REFRESHING(手指已经松开,header位于顶部,箭头隐藏,progressbar旋转,提示“正在刷新”)
下面分析代码:
public class MyListView extends ListView implements OnScrollListener {
@Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// TODO Auto-generated method stub
firstVisibleIndex = firstVisibleItem;
}
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
}
自定义ListView,实现OnscrollListener接口,目的是实时得到位于ListView顶部的是第几行。
// get all the view instances in the header
private void init(Context context) {
setOnScrollListener(this);
LayoutInflater inflater = LayoutInflater.from(context);
headerView = inflater.inflate(R.layout.header, null);
arrowImageView = (ImageView) headerView.findViewById(R.id.img_arrow);
refreshPB = (ProgressBar) headerView.findViewById(R.id.progress_refresh);
tipsTextView = (TextView) headerView.findViewById(R.id.text_tips);
timeTextView = (TextView) headerView.findViewById(R.id.text_last_refresh_time);
timeTextView.setText(getTime());
// arrowRotateAnim = createAnimation();
arrowRotateAnim = AnimationUtils.loadAnimation(context, R.anim.arrow_rotate);
// measure the width&height of the headerView, before this the width&height equals 0
measureView(headerView);
headerHeight = headerView.getMeasuredHeight();
// hide the headerView on the top
headerView.setPadding(0, -headerHeight, 0, 0);
// force redraw the view
headerView.invalidate();
addHeaderView(headerView);
state = DONE;
changeHeaderViewbyState();
}
通过init进行初始化,注册OnscrollListener、得到header中所有View、初始化动画、测量header、获得header的高度、将header隐藏、设置ListView的header、设置默认状态,根据状态确定headerView的显示内容。其中非常重要的是measureView和setPadding,前者用来测量header的长宽,后者用来设置header的显示效果(隐藏/部分隐藏)。
private void measureView(View child) {
ViewGroup.LayoutParams p = child.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
}
int childWidthSpec = ViewGroup.getChildMeasureSpec(0, 0 + 0, p.width);
int lpHeight = p.height;
int childHeightSpec;
if (lpHeight > 0) {
childHeightSpec = MeasureSpec.makeMeasureSpec(lpHeight, MeasureSpec.EXACTLY);
} else {
childHeightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
}
child.measure(childWidthSpec, childHeightSpec);
}
对headerView进行测量,此方法与ListView源码中添加每一个Item时候所调用的相同,具体功能就是测量长宽,在调用此方法之前headerView的长宽都是0(其实代码不是很看得懂。。。)。要求headerView必须是LinearLayout,否则会出错(我本来使用的RelativeLayout,出错了,在最外层又加了一个LinearLayout)
private Animation createAnimation() {
// TODO Auto-generated method stub
Animation animation = new RotateAnimation(0, -180, RotateAnimation.RELATIVE_TO_SELF, 0.5f,
RotateAnimation.RELATIVE_TO_SELF, 0.5f);
animation.setInterpolator(new LinearInterpolator());
animation.setDuration(250);
animation.setFillAfter(true);
return animation;
}
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:fillAfter="true" >
<rotate
android:fromDegrees="0"
android:toDegrees="180"
android:pivotX="50%"
android:pivotY="50%"
android:duration="500" />
</set>
两种实现动画的方法,一种是直接通过代码,一种是在res/anim文件夹下创建arrow_rotate文件
// prepare different header according to the state
private void changeHeaderViewbyState() {
// TODO Auto-generated method stub
switch (state) {
case DONE:
case PULL_TO_REFRESH:
arrowImageView.setVisibility(View.VISIBLE);
refreshPB.setVisibility(View.GONE);
tipsTextView.setText("下拉刷新");
headerView.setPadding(0, -headerHeight, 0, 0);
break;
case RELEASE_TO_REFRESH:
tipsTextView.setText("松开刷新");
// arrowImageView.setAnimation(arrowRotateAnim);
arrowImageView.clearAnimation();
arrowImageView.startAnimation(arrowRotateAnim);
break;
case REFRESHING:
arrowImageView.clearAnimation();
arrowImageView.setVisibility(View.GONE);
refreshPB.setVisibility(View.VISIBLE);
tipsTextView.setText("正在刷新");
timeTextView.setText(getTime());
headerView.setPadding(0, 0, 0, 0);
break;
default:
break;
}
}
根据不同的state,设置header的显示状态。
@Override
public boolean onTouchEvent(MotionEvent ev) {
复写onTouchEvent函数,对触摸事件进行监听,在这个函数中处理下拉刷新的手势。下面是此函数中的内容:
首先说明几个变量:
private boolean refreshable = false; // 用于确定是否对Touch事件进行分析和响应。只有当设置了OnRefreshListener时才会被置为true
private int startY = 0;// 当ListView位于顶部时手指触摸位置的Y值
private boolean isRecored;// 第一次记录下startY后置为true,当手指松开是置为false。用于确保一个拖拽过程中只记录一次startY
以下为具体代码:
if (refreshable) {
switch (ev.getAction()) {
当设置了OnRefreshListener之后,根据不同的Touch Action进行处理。
case MotionEvent.ACTION_DOWN:
if (firstVisibleIndex == 0 && !isRecored) {
// 如果ListView正好在顶部而且y值没有被记录过
startY = (int) ev.getY();
isRecored = true;
}
break;
case MotionEvent.ACTION_MOVE:
int tmpY = (int) ev.getY(); if (firstVisibleIndex == 0 && !isRecored) {
// 如果ListView正好在顶部而且y值没有被记录过
startY = tmpY;
isRecored = true;
}
// if has started to drag, and state is release_to_refresh
if (state == RELEASE_TO_REFRESH && (tmpY - startY) / RATIO < headerHeight) {
if ((tmpY - startY) > 0) {
// if state is to change to pull_to_refresh
state = PULL_TO_REFRESH;
changeHeaderViewbyState();
} else if ((tmpY - startY) <= 0) {
// if state is to change to done
state = DONE;
changeHeaderViewbyState();
}
}
// if has started to drag, but state is pull_to_refresh
// and state is to change to release_to_refresh
if (state == PULL_TO_REFRESH && (tmpY - startY) / RATIO > headerHeight) {
state = RELEASE_TO_REFRESH;
changeHeaderViewbyState();
}
// if has not started to drag
if (state == DONE && tmpY - startY > 0) {
state = PULL_TO_REFRESH;
changeHeaderViewbyState();
}
// change the padding
if (state == PULL_TO_REFRESH || state == RELEASE_TO_REFRESH) {
headerView.setPadding(0, -headerHeight + (tmpY - startY) / RATIO, 0, 0);
}
break;
case MotionEvent.ACTION_UP:
if (state == PULL_TO_REFRESH) {
state = DONE;
changeHeaderViewbyState();
} else if (state == RELEASE_TO_REFRESH) {
state = REFRESHING;
changeHeaderViewbyState();
new RefreshAsync().execute();
}
isRecored = false;
break;
如果当松开的时候state是RELEASE_TO_REFRESH,则异步进行刷新。
private class RefreshAsync extends AsyncTask<Void, Void, Boolean> {
@Override
protected Boolean doInBackground(Void... params) {
// TODO Auto-generated method stub
boolean result = false;
if (refreshListener != null) {
result = refreshListener.onRefresh();
}
return result;
}
@Override
protected void onPostExecute(Boolean result) {
// TODO Auto-generated method stub
state = DONE;
changeHeaderViewbyState();
if (result) {
Toast.makeText(getContext(), "refresh success", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getContext(), "refresh fail", Toast.LENGTH_SHORT).show();
}
}
}
刷新操作是通过调用OnRefreshListener的onRefresh方式实现的。OnRefreshListenerListener是自定义的一个接口:
public interface OnRefreshListener {
public boolean onRefresh();
}
Activity通过实现这个接口来自定义刷新时进行的具体操作:
public class MainActivity extends Activity implements OnRefreshListener {
@Override
public boolean onRefresh() {
// TODO Auto-generated method stub
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
return true;
}
Demo下载链接:http://download.csdn.net/detail/u011268102/6018363