ListView下拉刷新

package com.example.hunxiao;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import android.content.Context;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;

/**
 * @date 2013-7-26
 */
public class DropListivew extends ListView implements android.widget.AbsListView.OnScrollListener {

	public DropListivew(Context context) {
		super(context);
		init();
	}

	public DropListivew(Context context, AttributeSet attrs) {
		super(context, attrs);
		init();
	}

	public DropListivew(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
		init();
	}

	/*
	 * ==================列表顶部的控件集合==================
	 */
	private ProgressBar progressBar_top = null;
	private ImageView imageView_top = null;
	private TextView txtTime_top, txtRefresh_top;
	/** 列表顶部的视图 */
	private LinearLayout topViewItem = null;
	/** 列表顶部视图的高 */
	private int topViewItemHeight = 0;
	/*
	 * =====================================================
	 */

	/*
	 * ==================列表底部的控件集合==================
	 */
	private LinearLayout bottomViewItem = null;
	private ProgressBar progressBar_bottom = null;
	private TextView txtBottom = null;
	/*
	 * =====================================================
	 */

	/*
	 * ==================用于逻辑判断的4个变量==================
	 */
	/** 第一行是否在屏幕上 */
	private boolean isFirstItemShow = false;// 第一行即加载行,只要把列表滚动到顶,不管加载行被压缩了还是被拉开了,都会认为显示在了屏幕上
	/** 是否有加载意图,即加载第一行的意图 */
	private boolean isWantToExtrude = true;
	/** 是否已经拉开 */
	private boolean isExtruded = false;
	/** 是否已经在加载 */
	private boolean isLoading = false;
	/*
	 * =====================================================
	 */

	/** 时间格式化 */
	private SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");

	/** 初始化 */
	private void init() {
		setOnScrollListener(this);
		LayoutInflater layoutInflater = (LayoutInflater) getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE);

		// 初始化列表底部工具条
		bottomViewItem = (LinearLayout) layoutInflater.inflate(R.layout.base_list_item_bottom, this, false);
		progressBar_bottom = (ProgressBar) bottomViewItem.findViewById(R.id.progressBar1);
		txtBottom = (TextView) bottomViewItem.findViewById(R.id.textView1);

		// 初始化列表顶部工具条
		topViewItem = (LinearLayout) layoutInflater.inflate(R.layout.base_list_item_top, this, false);
		topViewItem.setOnClickListener(null);// 使它不能作为list的一个item来点击
		progressBar_top = (ProgressBar) topViewItem.findViewById(R.id.progressBar1);
		progressBar_top.setVisibility(View.INVISIBLE);
		imageView_top = (ImageView) topViewItem.findViewById(R.id.imageView1);
		txtTime_top = (TextView) topViewItem.findViewById(R.id.textView2);
		txtRefresh_top = (TextView) topViewItem.findViewById(R.id.textView1);

		/*
		 * 3步隐藏该topViewItem: 1.测出其加入屏幕后的长和宽,因为现在它没加入屏幕,直接获取其长宽是无效的 2.得到测出的高度
		 * 3.将上边距设置为负高度,相当于把高度变成了0,达到隐藏的效果(顺带一提:topViewItem即使通过padding设置其高度为0
		 * ,在列表中,它仍然会占据一行的像素,列表仍然认为它是第0行)
		 */
		// 第一步
		measureView(topViewItem);
		// 第二步
		topViewItemHeight = topViewItem.getMeasuredHeight();
		// 第三步
		topViewItem.setPadding(topViewItem.getPaddingLeft(), -1 * topViewItemHeight, topViewItem.getPaddingRight(), topViewItem.getPaddingBottom());
		// 加入到列表中
		addHeaderView(topViewItem);
	}

	/*
	 * 当列表以任何形式(自动滚或者手拖着滚)停止滚动后,如果第一行已经出现了,那么认为已经有了加载的意图 如果第一行没有出现,那么认为没有加载的意图
	 */
	@Override
	public void onScrollStateChanged(AbsListView view, int scrollState) {
		if (isLoading)// 已经在加载则不作任何处理
		{
			return;
		}
		// 当列表以任何形式(自动滚或者手拖着滚)停止滚动时
		if (scrollState == SCROLL_STATE_IDLE) {
			if (isFirstItemShow) {
				isWantToExtrude = true;
				// System.out.println("意图被onScrollStateChanged置为true");
			} else {
				isWantToExtrude = false;
				// System.out.println("意图被onScrollStateChanged置为false");
			}
		}
	}

	/*
	 * 在列表滚动过程中,根据屏幕最顶行是不是第0行,来判断:第0行是否出现在屏幕上,是否存在加载的意图
	 */
	@Override
	public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
		if (isLoading)// 已经在加载则不作任何处理
		{
			return;
		}
		if (firstVisibleItem == 0) {
			isFirstItemShow = true;
			// System.out.println("isFirstItemShow被onScroll置为true");
		} else {
			isFirstItemShow = false;
			isWantToExtrude = false;
			// System.out.println("意图被onScroll置为false");
		}
	}

	/** 上一次手指触摸屏幕的Y轴坐标 */
	private float lastActionDown_Y = 0;

	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		boolean result = super.onTouchEvent(ev);
		if (isLoading)// 已经在加载则不作任何处理
		{
			return result;
		}

		switch (ev.getAction()) {

		// 按下手指时,获得按下y轴位置,用于在拖动时计算手指拖动距离
		case MotionEvent.ACTION_DOWN:
			lastActionDown_Y = ev.getY();
			break;
		/*
		 * 1.如果有加载的意图,则拉伸第一行。但加载意图只是说他可能要拉,有加载意图的同时还需要手指往下滑动才算真的要加载.
		 * 当手指往上滑动时,用户并不想拉开,滑动Y轴的值为负(刚开始接近0,后面绝对值逐渐增大).但加载意图在那个一瞬间可能
		 * 还没有立即被onScroll重置为false,在那一瞬间可能就会把拉伸条的paddingTop置为非常接近0的负值,
		 * 很明显就会把拉伸条显示出来.因此要判断大于0才去做这个动作 2.如果没有加载的意图,则有两种可能。一种是平常的触摸,不用作任何处理。
		 * 另一种是当时已经拉开了,用户又不想拉了,所以又向上推了回去(大家应该深有体会)。推回去后,加载意图被onScroll函数制为false。
		 * 在这种情况下,应该把第一行重新压缩回原状。
		 */
		case MotionEvent.ACTION_MOVE:
			// 手指滑动的距离
			float gapValue_y = ev.getY() - lastActionDown_Y;

			if (isWantToExtrude) {
				if (gapValue_y > 0) {// 超过5个像素再执行,这是为了提升用户体验,免得一碰就抖,跟本身的逻辑没关系
					isExtruded = true;
					// 新的PaddingTop应该是滑动的距离减去总高,才会使加载行有慢慢拉宽的效果
					int newPaddingTop = (int) gapValue_y - topViewItemHeight;
					topViewItem.setPadding(0, newPaddingTop, 0, 0);
					// 如果新的PaddingTop大于等于0,说明已经完全被拉开,设置控件相关动作
					if (newPaddingTop >= 0) {
						setControl(2);
					} else {
						setControl(1);
					}
				} else {
					// System.out.println("小于0,往上推,什么都不干,坐等意图被重置");
				}
			} else {
				if (isExtruded) {
					// 又推回去了
					isExtruded = false;
					topViewItem.setPadding(0, -1 * topViewItemHeight, 0, 0);
					// System.out.println("重置");
				}
			}
			break;

		// 拿起手指时,平常状态下什么都不用做。
		// 拿起手指时,如果已经完全被拉开,则开始加载
		case MotionEvent.ACTION_UP:
			if (isExtruded) {
				// System.out.println("paddingTop:" +
				// topViewItem.getPaddingTop());
				// System.out.println("topViewItemHeight:" + topViewItemHeight);
				if (topViewItem.getPaddingTop() >= 0) {
					if (this.onPDListListener != null) {
						isLoading = true;
						topViewItem.setPadding(0, 0, 0, 0);
						setControl(3);
						this.onPDListListener.onRefresh();
					} else {
						topViewItem.setPadding(0, -1 * topViewItemHeight, 0, 0);
						isExtruded = false;
						setSelection(0);// 退回首行
					}
				} else {
					topViewItem.setPadding(0, -1 * topViewItemHeight, 0, 0);
					isExtruded = false;
					setSelection(0);// 退回首行
				}
			}
			break;

		default:
			break;
		}

		return result;
	}

	// 提升性能,防止无意义的重复赋值
	private int controlState = -1;

	/**
	 * 设置控件的显示内容
	 * 
	 * @param state 0表示被用户重置,1表示下拉刷新,2表示松开刷新,3表示正在刷新
	 */
	private void setControl(int state) {
		if (controlState != state) {
			controlState = state;
			if (state == 0) {
				imageView_top.setVisibility(View.VISIBLE);
				progressBar_top.setVisibility(View.INVISIBLE);
			} else if (state == 1) {
				txtRefresh_top.setText("下拉更新");
				imageView_top.setImageResource(R.drawable.ic_down);
			} else if (state == 2) {
				txtRefresh_top.setText("释放立即更新");
				imageView_top.setImageResource(R.drawable.ic_up);
			} else if (state == 3) {
				imageView_top.setVisibility(View.INVISIBLE);
				progressBar_top.setVisibility(View.VISIBLE);
				txtRefresh_top.setText("正在更新...");
			}
		}

	}

	/* =======================监听部分的公开函数====================== */
	/** 设置刷新监听 */
	public void setOnPDListen(OnPDListListener onPDListListener) {
		this.onPDListListener = onPDListListener;
	}

	/** 移除刷新监听 */
	public void removePDListen() {
		this.onPDListListener = null;
	}

	/* ==================================================== */

	/* ===============加载更多部分的公开函数============= */
	private boolean isLoadingMoreViewEnabled = true;

	/** 初始化加载更多的工具条,并且添加到列表底部 */
	public void loadingMoreView_init() {
		// 初始化列表底部的控件
		addFooterView(bottomViewItem);
		bottomViewItem.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				if (onPDListListener != null && isLoadingMoreViewEnabled) {
					progressBar_bottom.setVisibility(View.VISIBLE);
					txtBottom.setText("正在加载...");
					onPDListListener.onloadMore();
				}
			}
		});
	}

	/** 完成加载更多 */
	public void loadingMore_finish() {
		progressBar_bottom.setVisibility(View.INVISIBLE);
		txtBottom.setText("点击加载更多");
	}

	/** 设置是否启用 */
	public void loadingMore_enabled(boolean enabled) {
		progressBar_bottom.setVisibility(View.INVISIBLE);
		isLoadingMoreViewEnabled = enabled;
		if (enabled) {
			txtBottom.setText("点击加载更多");
		} else {
			txtBottom.setText("已经到达最后一条");
		}
	}

	/* ==================================================== */

	/* ====================刷新部分的公开函数==================== */
	/** 弹出刷新条 并触发onRefresh接口 */
	public void startRefresh() {
		if (!isLoading && this.onPDListListener != null) {
			isLoading = true;
			topViewItem.setPadding(0, 0, 0, 0);
			setControl(3);
			setSelection(0);// 选中首行
			this.onPDListListener.onRefresh();
		}
	}

	/**
	 * 结束刷新
	 * 
	 * @param isUpdateTime 是否更新时间,刷新失败可以不更新
	 */
	public void stopRefresh(boolean isUpdateTime) {
		// 还原所有变量,首行重置回初始值,选中首行
		if (isLoading) {
			// 重置所有变量
			isFirstItemShow = false;
			isWantToExtrude = true;
			isExtruded = false;
			isLoading = false;
			// 重置下拉行控件的内容
			setControl(0);
			// 更新时间
			if (isUpdateTime) {
				txtTime_top.setText("上一次更新时间:" + sdf.format(Calendar.getInstance().getTime()));
			}
			topViewItem.setPadding(0, -1 * topViewItemHeight, 0, 0);
			setSelection(0);
		}
	}

	/* ==================================================== */

	/** 为View测量长宽 因为View在加入到界面之前,直接去获取长宽是无效的,跟在Activity的onCreat时无法获取控件长宽一个道理 */
	private static 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);
	}

	private OnPDListListener onPDListListener = null;

	public interface OnPDListListener {
		public void onRefresh();

		public void onloadMore();
	}

}
base_list_item_top.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" >

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginBottom="15dp"
        android:layout_marginTop="30dp" >

        <TextView
            android:id="@+id/textView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerHorizontal="true"
            android:text="下拉刷新"
            android:textSize="14sp" />

        <TextView
            android:id="@+id/textView2"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_below="@+id/textView1"
            android:layout_centerHorizontal="true"
            android:text="上一次更新时间:未更新"
            android:textSize="12sp" />

        <ImageView
            android:id="@+id/imageView1"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="70dp"
            android:src="@drawable/drop_down_list_arrow" />

        <ProgressBar
            android:id="@+id/progressBar1"
            style="?android:attr/progressBarStyleSmall"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerVertical="true"
            android:layout_marginLeft="70dp" />
    </RelativeLayout>

</LinearLayout>
base_list_item_bottom.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    android:gravity="center_horizontal"
    android:orientation="horizontal"
    android:padding="8dip" >

    <ProgressBar
        android:id="@+id/progressBar1"
        style="?android:attr/progressBarStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

    <TextView
        android:id="@+id/textView1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>



你可能感兴趣的:(ListView下拉刷新)