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的headerviewprivate ImageView mImage; // headerview中图片private ProgressBar mProgress; // headerview中的progressbarprivate 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();} // 初始化viewprivate void initView() {// 加载headerviewmHeaderView = 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// 为负的高度,则隐藏掉headerviewsetPaddingTop(-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的paddingprivate void setPaddingTop(int top) {mHeaderView.setPadding(mHeaderView.getPaddingLeft(),top,mHeaderView.getPaddingRight(),mHeaderView.getPaddingBottom()); invalidate();} // 测量viewprivate void measureView(View view) {// 先获取LayoutParamsViewGroup.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的Specif (height > 0) {heightMeasureSpec = MeasureSpec.makeMeasureSpec(height,MeasureSpec.EXACTLY);} else {heightMeasureSpec = MeasureSpec.makeMeasureSpec(0,MeasureSpec.UNSPECIFIED);} // 测量view,这样就可以获取headerview的高度了// 否则headerview的高度获取出来为0// MATCH_PARENT=-1, WRAP_CONTENT=-2view.measure(widthMeasureSpec, heightMeasureSpec);} // 滑动状态该类@Overridepublic void onScrollStateChanged(AbsListView view, int scrollState) {// 保存下当前的状态mScrollState = scrollState;} @Overridepublic void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {// 保存当前第一个可见项mFirstVisibleItem = firstVisibleItem;} // 监听touch事件@Overridepublic 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); // 隐藏progressbarmImage.setVisibility(View.VISIBLE); // 将图片显示出来mImage.clearAnimation(); // 清除动画mImage.startAnimation(mAnim1); // 设置旋转动画mText.setText("下拉刷新!"); // 更新textview的文本break;case STATE_RELEASE: // 释放状态mProgress.setVisibility(View.GONE); // 隐藏progressbarmImage.setVisibility(View.VISIBLE); // 将图片显示出来mImage.clearAnimation(); // 清除动画mImage.startAnimation(mAnim2); // 设置旋转动画mText.setText("释放加载新数据!"); // 更新textview的文本break;case STATE_LOADING: // 数据加载状态setPaddingTop(mHeaderHeight); // 设置paddingtop为headerview的高度mProgress.setVisibility(View.VISIBLE); // 显示progressbarmImage.clearAnimation(); // 清除动画mImage.setVisibility(View.GONE); // 隐藏imageviewmText.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();}}
Header_layout.xml file used in mylistview:
<?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>
Usage:
In the layout file:
<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>
In mainactivity:
public class MainActivity extends Activity {private MyListView mListView;// 模拟数据private String[] mData = { "aaa", "bbb", "ccc", "ddd", "eee", "fff", "ggg","hhh", "iii" };@Overrideprotected 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() {@Overridepublic void onPull() {// 模拟获取新数据// 延迟2s后更新前两个数据Handler handler = new Handler();handler.postDelayed(new Runnable() {@Overridepublic void run() {for (int i = 0; i < 2; i++) {mData[i] = "new data " + new Random().nextInt(100);adapter.notifyDataSetChanged();mListView.endToRefresh(); // 通知MyListView数据加载完毕了}}}, 2000);}});}}
Listview pull-down refresh