Android中自定义可以下拉刷新的ListView(附demo)

下拉刷新的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 


 

 

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值