public class MyListView extends ListView implements OnScrollListener { private static final int STATE_NORMAL = 0; // 正常状态 private static final int STATE_PULL = 1; // 下来状态 private static final int STATE_RELEASE = 2; // 释放状态 private static final int STATE_LOADING = 3; // 数据加载状态 private View mHeaderView; // listview的headerview private ImageView mImage; // headerview中图片 private ProgressBar mProgress; // headerview中的progressbar private TextView mText; // headerview中的文本 private RotateAnimation mAnim1; // headerview中图片的旋转动画 private RotateAnimation mAnim2; // 同上 private boolean isNowAtUp; // 判断当前是不是在listview的最上面 private int mHeaderHeight; // headerview的高度 private int mFirstVisibleItem; // 保存listview中第一个可见项 private int mScrollState; // 保存listview滑动的状态 private int mState; // 保存判断的状态,同上面的常量进行比较, private int mStartY; // 开始下拉的y值 private OnPullRefreshListener mListener; // 回调接口 public MyListView(Context context, AttributeSet attrs) { super(context, attrs); initView(); } public MyListView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(); } // 初始化view private void initView() { // 加载headerview mHeaderView = LayoutInflater.from(getContext()).inflate( R.layout.header_layout, null); //获取headerview中组件 mImage = (ImageView) mHeaderView.findViewById(R.id.image); mProgress = (ProgressBar) mHeaderView.findViewById(R.id.progress); mText = (TextView) mHeaderView.findViewById(R.id.text); // 初始化动画 initAnimation(); // 因为下面要用到headerview的高度,但在处理化的时候还没有进行onMeasure // 所以要手工进行测量 measureView(mHeaderView); // 获取headerview的高度 mHeaderHeight = mHeaderView.getMeasuredHeight(); // 设置headerview的上padding // 为负的高度,则隐藏掉headerview setPaddingTop(-mHeaderHeight); // 将headerview添加到listview中 addHeaderView(mHeaderView); // 监听滚动 setOnScrollListener(this); } // 初始化动画 private void initAnimation() { mAnim1 = new RotateAnimation(180, 0, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); mAnim2 = new RotateAnimation(0, 180, RotateAnimation.RELATIVE_TO_SELF, 0.5f, RotateAnimation.RELATIVE_TO_SELF, 0.5f); mAnim1.setDuration(500); mAnim2.setDuration(500); mAnim1.setFillAfter(true); mAnim2.setFillAfter(true); } // 设置headerview的padding private void setPaddingTop(int top) { mHeaderView.setPadding( mHeaderView.getPaddingLeft(), top, mHeaderView.getPaddingRight(), mHeaderView.getPaddingBottom()); invalidate(); } // 测量view private void measureView(View view) { // 先获取LayoutParams ViewGroup.LayoutParams lp = view.getLayoutParams(); if (null == lp) { lp = new ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } // 第一个参数是spec // 第二个参数是padding // 第三个参数是我期望的大小 // 如果第三个参数小于0 // 则返回的结果是第一个参数减去第二个参数 // 如果第三个参数不小于0 // 则返回第三个参数 int widthMeasureSpec = ViewGroup.getChildMeasureSpec(0, 0, lp.width); int height = lp.height; int heightMeasureSpec; // 生成height的Spec if (height > 0) { heightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); } else { heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); } // 测量view,这样就可以获取headerview的高度了 // 否则headerview的高度获取出来为0 // MATCH_PARENT=-1, WRAP_CONTENT=-2 view.measure(widthMeasureSpec, heightMeasureSpec); } // 滑动状态该类 @Override public void onScrollStateChanged(AbsListView view, int scrollState) { // 保存下当前的状态 mScrollState = scrollState; } @Override public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) { // 保存当前第一个可见项 mFirstVisibleItem = firstVisibleItem; } // 监听touch事件 @Override public boolean onTouchEvent(MotionEvent ev) { switch (ev.getAction()) { case MotionEvent.ACTION_DOWN: // 如果是按下操作 // 并且现在第一个可见项是listview的第一项 // 即:现在在最上面 if (mFirstVisibleItem == 0) { isNowAtUp = true; // 记录现在是在最上面 mStartY = (int) ev.getY(); // 获取点击的y坐标 } break; case MotionEvent.ACTION_MOVE: onMove(ev); // 在onMove中处理移动事件 break; case MotionEvent.ACTION_UP: // 如果当前状态是可释放状态 if(mState == STATE_RELEASE) { // 那个up后, 修改状态为数据加载状态 mState = STATE_LOADING; // 刷新界面 refreshView(); // 调用回调接口中的方法 // 具体实现在activity中 mListener.onPull(); }else if(mState == STATE_PULL){ // 如果是下拉状态 mState = STATE_NORMAL; // 则更改状态为正常状态,因为还没有拉到加载数据的程度 refreshView(); // 刷新界面 } break; } return super.onTouchEvent(ev); } // 刷新界面,其实是更改headerview中子view的状态 private void refreshView() { switch (mState) { case STATE_NORMAL: setPaddingTop(-mHeaderHeight); // 如果是正常状态,则将headerview隐藏了 break; case STATE_PULL: // 如果是下拉状态 mProgress.setVisibility(View.GONE); // 隐藏progressbar mImage.setVisibility(View.VISIBLE); // 将图片显示出来 mImage.clearAnimation(); // 清除动画 mImage.startAnimation(mAnim1); // 设置旋转动画 mText.setText("下拉刷新!"); // 更新textview的文本 break; case STATE_RELEASE: // 释放状态 mProgress.setVisibility(View.GONE); // 隐藏progressbar mImage.setVisibility(View.VISIBLE); // 将图片显示出来 mImage.clearAnimation(); // 清除动画 mImage.startAnimation(mAnim2); // 设置旋转动画 mText.setText("释放加载新数据!"); // 更新textview的文本 break; case STATE_LOADING: // 数据加载状态 setPaddingTop(mHeaderHeight); // 设置paddingtop为headerview的高度 mProgress.setVisibility(View.VISIBLE); // 显示progressbar mImage.clearAnimation(); // 清除动画 mImage.setVisibility(View.GONE); // 隐藏imageview mText.setText("正在加载数据..."); // 更新文本 break; } } // 处理action_move事件 private void onMove(MotionEvent ev) { // 如果当前不是在顶部,直接返回 if (!isNowAtUp) { return; } int nowY = (int) ev.getY(); // 记录当前的y坐标 int space = nowY - mStartY; // 计算下拉的程度 int top = space - mHeaderHeight; // 计算top的值 switch (mState) { case STATE_NORMAL: // 如果是正常状态 if (space > 0) { // 如果下拉了 mState = STATE_PULL; // 则 修改状态为下拉 refreshView(); // 并刷新headerview } break; case STATE_PULL: // 如果是下来状态 setPaddingTop(top); // 不断修改headerview的paddingtop // 如果达到条件,并且还是在下拉中 if (space >= mHeaderHeight + 30 && mScrollState == OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) { mState = STATE_RELEASE; // 修改状态为可释放状态 refreshView(); // 刷新headerview } break; case STATE_RELEASE: // 如果是可释放状态 setPaddingTop(top); // 不断修改headerview的paddingtop // 如果下拉程度在STATE_PULL区间 if (space > 0 && space <= mHeaderHeight + 30) { mState = STATE_PULL; // 则修改状态为下来状态,这样是又慢慢拉回去了 refreshView(); // 刷新headerview } else if (space <= 0) { // 如果没有下拉的空间了 mState = STATE_NORMAL; // 修改状态为正常状态,拉了一圈又回退回去了 isNowAtUp = false; // 还原默认值 refreshView(); // 刷新headerview } break; } } // 外部调用的方法 // 通知我新数据加载完毕 // 还原状态为正常状态 public void endToRefresh() { mState = STATE_NORMAL; refreshView(); } // 设置加载数据的回调接口 public void setOnPullRefreshListener(OnPullRefreshListener l) { mListener = l; } // 定义一个回调接口 public interface OnPullRefreshListener { public void onPull(); } }
MyListView中使用的header_layout.xml文件:
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="wrap_content" android:background="@android:color/darker_gray" android:orientation="vertical" > <RelativeLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:paddingBottom="10dip" android:paddingLeft="30dip" android:paddingRight="30dip" android:paddingTop="10dip" > <LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" android:orientation="horizontal" > <ImageView android:id="@+id/image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:src="@drawable/arrow" /> <ProgressBar android:id="@+id/progress" style="?android:attr/progressBarStyleSmall" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:visibility="gone" /> <TextView android:id="@+id/text" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" android:text="下拉可以刷新" /> </LinearLayout> </RelativeLayout> </LinearLayout>
使用:
在布局文件中:
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity" > <org.load.listview.MyListView android:id="@+id/list" android:layout_width="match_parent" android:layout_height="wrap_content" /> </RelativeLayout>
在MainActivity中:
public class MainActivity extends Activity { private MyListView mListView; // 模拟数据 private String[] mData = { "aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg", "hhh", "iii" }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mListView = (MyListView) findViewById(R.id.list); // 数据适配器 final ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mData); // 设置适配器 mListView.setAdapter(adapter); // 这里就用到了回调接口 // 表示当可以加载新数据时干什么 mListView.setOnPullRefreshListener(new OnPullRefreshListener() { @Override public void onPull() { // 模拟获取新数据 // 延迟2s后更新前两个数据 Handler handler = new Handler(); handler.postDelayed(new Runnable() { @Override public void run() { for (int i = 0; i < 2; i++) { mData[i] = "new data " + new Random().nextInt(100); adapter.notifyDataSetChanged(); mListView.endToRefresh(); // 通知MyListView数据加载完毕了 } } }, 2000); } }); } }
原文:http://my.oschina.net/qibin/blog/325500