`

android开发中ListView的妙用之:下拉刷新与到底部载入更多源码

阅读更多
今天项目也快结束了,就写篇博客吧,记录一下我对ListView的下拉刷新与底部载入更多的一点理解
首先我们知道ListView是没有下拉刷新的接口的,我们要借助现有的功能去扩展更多的功能我们就需要对ListView做一点修改,使它满足我们的需求:
一、ListView的表面理解就是List的格式显示的View,也就是要一行一行的显示,对ListView添加适配器这是必不可少的。所以我们修改以后的ListView当然也可以添加适配器等等常见的对ListView的操作;
二、要捕捉我们对列表界面的操作,下拉或者滚动到底部必须要有对事件的监听器,这里就尤为重要了,我们知道对按钮添加监听器,我们可以捕获到我们对某个按钮施加了操作,然后就执行我们的操作,这里的下拉或者滚动到底部,我们当然也需要捕捉到这个动作,然后执行我们想要执行的动作;
三、原理我们知道啦,现在就剩下怎么去捕获这个动作,然后捕获动作后我们怎么添加入我们想要的操作。只要捕获到动作,你想干什么我不关心,因为需求各式各样;
四、在捕获到动作以后我们开始给用户点反应。。。。。
下边我们来看看这个Demo的代码
一、继承ListView

package com.example.pulldownview;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ListView;


/**
 * <p>一个可以监听ListView是否滚动到最顶部或最底部的自定义控件</p>
 * 只能监听由触摸产生的,如果是ListView本身Flying导致的,则不能监听</br>
 * 如果加以改进,可以实现监听scroll滚动的具体位置等
 * @author solo ho</br> Email:kjsoloho@gmail.com
 */

public class ScrollOverListView extends ListView {

	private static final String TAG = "ScrollOverListView";
	private static final boolean DEBUG = false;
	private int mLastY;
	private int mTopPosition;
	private int mBottomPosition;

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

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

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

	private void init(){
		mTopPosition = 0;
		mBottomPosition = 0;
	}
	
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		if (ev.getAction() == MotionEvent.ACTION_DOWN) {
			if (DEBUG) Log.d(TAG, "onInterceptTouchEvent Action down");
			mLastY = (int) ev.getRawY();
		}
		return super.onInterceptTouchEvent(ev);
	}
	
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		final int action = ev.getAction();
		final int y = (int) ev.getRawY();
		
		boolean isHandled = false;
		switch(action){
			case MotionEvent.ACTION_DOWN:{
				if (DEBUG) Log.d(TAG, "action down");
				mLastY = y;
				isHandled = mOnScrollOverListener.onMotionDown(ev);
				if (isHandled) {
					break;
				}
				break;
			}
			
			case MotionEvent.ACTION_MOVE:{
				if (DEBUG) Log.d(TAG, "action move");
				final int childCount = getChildCount();
				if(childCount == 0) {
					break;
				}
				
				final int itemCount = getAdapter().getCount() - mBottomPosition;
				
				final int deltaY = y - mLastY;
				if (DEBUG) Log.d(TAG, "lastY=" + mLastY +" y=" + y);
				
				final int firstTop = getChildAt(0).getTop();
				final int listPadding = getListPaddingTop();
				
				final int lastBottom = getChildAt(childCount - 1).getBottom();
				final int end = getHeight() - getPaddingBottom();
				
				final int firstVisiblePosition = getFirstVisiblePosition();
				
				isHandled = mOnScrollOverListener.onMotionMove(ev, deltaY);
				
				if(isHandled){
					break;
				}
				
				//DLog.d("firstVisiblePosition=%d firstTop=%d listPaddingTop=%d deltaY=%d", firstVisiblePosition, firstTop, listPadding, deltaY);
				if (firstVisiblePosition <= mTopPosition && firstTop >= listPadding && deltaY > 0) {
					if (DEBUG) Log.d(TAG, "action move pull down");
		            isHandled = mOnScrollOverListener.onListViewTopAndPullDown(ev, deltaY);
		            if(isHandled){
		            	break;
		            }
		        }
				
				// DLog.d("lastBottom=%d end=%d deltaY=%d", lastBottom, end, deltaY);
		        if (firstVisiblePosition + childCount >= itemCount && lastBottom <= end && deltaY < 0) {
		        	if (DEBUG) Log.d(TAG, "action move pull up");
		        	isHandled = mOnScrollOverListener.onListViewBottomAndPullUp(ev, deltaY);
		        	if(isHandled){
		        		break;
		        	}
		        }
				break;
			}
			
			case MotionEvent.ACTION_CANCEL:
			case MotionEvent.ACTION_UP:{
				if (DEBUG) Log.d(TAG, "action move pull up");
				isHandled = mOnScrollOverListener.onMotionUp(ev);
				if (isHandled) {
					break;
				}
				break;
			}
		}
		
		mLastY = y;
		if (isHandled) {
			return true;
		}
		return super.onTouchEvent(ev);
	}
	
	
	/**空的*/
	private OnScrollOverListener mOnScrollOverListener = new OnScrollOverListener(){

		@Override
		public boolean onListViewTopAndPullDown(MotionEvent event, int delta) {
			return false;
		}

		@Override
		public boolean onListViewBottomAndPullUp(MotionEvent event, int delta) {
			return false;
		}

		@Override
		public boolean onMotionDown(MotionEvent ev) {
			return false;
		}

		@Override
		public boolean onMotionMove(MotionEvent ev, int delta) {
			return false;
		}

		@Override
		public boolean onMotionUp(MotionEvent ev) {
			return false;
		}
		
	};
	
	
	
	
	
	
	
	// =============================== public method ===============================

	/**
	 * 可以自定义其中一个条目为头部,头部触发的事件将以这个为准,默认为第一个
	 * 
	 * @param index 正数第几个,必须在条目数范围之内
	 */
	public void setTopPosition(int index){
		if(index < 0)
			throw new IllegalArgumentException("Top position must > 0");
		
		mTopPosition = index;
	}
	
	/**
	 * 可以自定义其中一个条目为尾部,尾部触发的事件将以这个为准,默认为最后一个
	 * 
	 * @param index 倒数第几个,必须在条目数范围之内
	 */
	public void setBottomPosition(int index){
		if(index < 0)
			throw new IllegalArgumentException("Bottom position must > 0");
		
		mBottomPosition = index;
	}

	/**
	 * 设置这个Listener可以监听是否到达顶端,或者是否到达低端等事件</br>
	 * 
	 * @see OnScrollOverListener
	 */
	public void setOnScrollOverListener(OnScrollOverListener onScrollOverListener){
		mOnScrollOverListener = onScrollOverListener;
	}
	
	/**
	 * 滚动监听接口</br>
	 * @see ScrollOverListView#setOnScrollOverListener(OnScrollOverListener)
	 * 
	 * @author solo ho</br> Email:kjsoloho@gmail.com
	 */
	public interface OnScrollOverListener {
		
		/**
		 * 到达最顶部触发
		 * 
		 * @param delta 手指点击移动产生的偏移量
		 * @return 
		 */
		boolean onListViewTopAndPullDown(MotionEvent event, int delta);

		/**
		 * 到达最底部触发
		 * 
		 * @param delta 手指点击移动产生的偏移量
		 * @return 
		 */
		boolean onListViewBottomAndPullUp(MotionEvent event, int delta);
		
		/**
		 * 手指触摸按下触发,相当于{@link MotionEvent#ACTION_DOWN}
		 * 
		 * @return 返回true表示自己处理
		 * @see View#onTouchEvent(MotionEvent)
		 */
		boolean onMotionDown(MotionEvent ev);
		
		/**
		 * 手指触摸移动触发,相当于{@link MotionEvent#ACTION_MOVE}
		 * 
		 * @return 返回true表示自己处理
		 * @see View#onTouchEvent(MotionEvent)
		 */
		boolean onMotionMove(MotionEvent ev, int delta);
		
		/**
		 * 手指触摸后提起触发,相当于{@link MotionEvent#ACTION_UP} 
		 * 
		 * @return 返回true表示自己处理
		 * @see View#onTouchEvent(MotionEvent)
		 */
		boolean onMotionUp(MotionEvent ev);
		
	}


}


二、下来刷新
package com.example.pulldownview;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.Animation;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.example.pulldownview.ScrollOverListView.OnScrollOverListener;

/**
 * 下拉刷新控件</br>
 * 真正实现下拉刷新的是这个控件,
 * ScrollOverListView只是提供触摸的事件等
 * @author Solo Email:kjsoloho@gmail.com
 */
public class PullDownView extends LinearLayout implements OnScrollOverListener, OnScrollListener {
	private static final String TAG = "PullDownView";
	private static final boolean DEBUG = false;
	
	private static final int AUTO_INCREMENTAL = 10;		// 自增量,用于回弹
	
	private static final int WHAT_SET_HEADER_HEIGHT = 1;// Handler what 设置高度
	
	private static SimpleDateFormat dateFormat = new SimpleDateFormat("MM-dd HH:mm");
	
	private View mHeaderView;
	private LayoutParams mHeaderViewParams;	
	private TextView mHeaderViewDateView;
	private TextView mHeaderTextView;
	private ImageView mHeaderArrowView;
	private View mHeaderLoadingView;
	private View mFooterView;
	private TextView mFooterTextView;
	private View mFooterLoadingView;
	private ScrollOverListView mListView;
	
	private OnPullDownListener mOnPullDownListener;
	private RotateAnimation mRotateOTo180Animation;
	private RotateAnimation mRotate180To0Animation;
	
	private Context mContext;
	private Field overScrollModeField;
	
	private int mMoveDeviation;				// 移动误差
	private int mHeaderIncremental;			// 头部文件自增量增量
	private int mDefaultHeaderViewHeight;	// 头部文件原本的高度
	private int mStartIndex;				// 当前List可显示第一项
	//private int mEndIndex;					// 当前List可显示最后一项
	
	private float mMotionDownLastY;	// 按下时候的Y轴坐标
	
	private boolean mEnablePullDown;		// 是否可以下拉
	private boolean mIsPullUpDone;			// 是否回推完成
	private boolean mEnableLoadMore;			// 是否启用更多
	private boolean mEnableAutoFetchMore;	// 是否允许自动获取更多
	private boolean mIsNoMoreData;			// 没有更多的数据了
	private boolean mIsDidLoad;				// 是否加载了数据
	
	// 头部文件的状态
	private static final int HEADER_VIEW_STATE_IDLE = 0;			// 空闲
	private static final int HEADER_VIEW_STATE_NOT_OVER_HEIGHT = 1;	// 没有超过默认高度
	private static final int HEADER_VIEW_STATE_OVER_HEIGHT = 2;		// 超过默认高度
	private int mHeaderViewState = HEADER_VIEW_STATE_IDLE;
	
	private static final int STATE_NONE = 0;
	private static final int STATE_REFRESHING = 1;		// 刷新中
	private static final int STATE_LOADING_MORE = 2;	// 加载更多中
	private static final int STATE_DRAGING = 4;			// 拖动中
	private static final int STATE_MOTION_DOWN = 8;		// 按下
	private int state = STATE_NONE;

	public PullDownView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initHeaderViewAndFooterViewAndListView(context);
	}

	public PullDownView(Context context) {
		super(context);
		initHeaderViewAndFooterViewAndListView(context);
	}
	
	/*
	 * ==================================
	 * Public method
	 * 外部使用,具体就是用这几个就可以了
	 * 
	 * ==================================
	 */
	
	/**
	 * 刷新事件接口
	 * @author Solo Email:kjsoloho@gmail.com
	 */
	public interface OnPullDownListener {
		void onRefresh();
		void onLoadMore();
	}
	
	/**
	 * 通知加载完了数据,要放在Adapter.notifyDataSetChanged后面
	 * 当你加载完数据的时候,调用这个notifyDidLoad()
	 * 才会隐藏头部,并初始化数据等
	 */
	public void notifyDidDataLoad(boolean isNoMoreData) {
		mIsDidLoad = true;
		mIsNoMoreData = isNoMoreData;
		mFooterView.setVisibility(View.VISIBLE);
		updateFooter();
		mListView.setFooterDividersEnabled(true);
		
		mHeaderViewParams.height = 0;
		mHeaderView.setLayoutParams(mHeaderViewParams);
		updateHeader();
		
		doListViewIdleActionOnDataDidLoad();
	}
	
	/**
	 * 通知已经刷新完了,要放在Adapter.notifyDataSetChanged后面
	 * 当你执行完刷新任务之后,调用这个notifyDidRefresh()
	 * 才会隐藏掉头部文件等操作
	 */
	public void notifyDidRefresh(boolean isNoMoreData) {
		mIsNoMoreData = isNoMoreData;
		updateFooter();
		
		state &= ~STATE_REFRESHING;
		mHeaderViewState = HEADER_VIEW_STATE_IDLE;
		setHeaderHeight(0);
		updateHeader();
		
		doListViewIdleActionOnDataDidLoad();
	}
	
	/**
	 * 通知已经获取完更多了,要放在Adapter.notifyDataSetChanged后面
	 * 当你执行完更多任务之后,调用这个notyfyDidMore()
	 * 才会隐藏加载圈等操作
	 */
	public void notifyDidLoadMore(boolean isNoMoreData) {
		mIsNoMoreData = isNoMoreData;
		state &= ~STATE_LOADING_MORE;
		updateFooter();
	}

	/**
	 * 设置监听器
	 * @param listener
	 */
	public void setOnPullDownListener(OnPullDownListener listener){
		mOnPullDownListener = listener;
	}

	/**
	 * 获取内嵌的listview
	 * @return ScrollOverListView
	 */
	public ScrollOverListView getListView(){
		return mListView;
	}

	/**
	 * 是否开启自动获取更多</br>
	 * 自动获取更多,并在触摸到达底部的时候自动刷新
	 * @param index 倒数第几个触发
	 */
	public void enableAutoFetchMore(boolean enable, int index){
		if(!mEnableLoadMore) return;
		mEnableAutoFetchMore = enable;
		if(enable){
			mListView.setBottomPosition(index);
		}else{
			updateFooter();
		}
	}
	
	/**
	 * 是否启用加载更多</br>
	 * 只能在初始化的时候调用
	 */
	public void enableLoadMore(boolean enable) {
		mEnableLoadMore = enable;
		if (!enable) {
			// TODO 暂时使用这种错开的方法
			// 更好的办法是写到属性里面
			mUIHandler.post(new Runnable() {
				
				@Override
				public void run() {
					removeFooter();
				}
			});
		}
	}
	
	/**
	 * 是否启用下拉刷新
	 */
	public void enablePullDown(boolean enable) {
		mEnablePullDown = enable;
	}
	
	/*
	 * ==================================
	 * Private method
	 * 具体实现下拉刷新等操作
	 * 
	 * ==================================
	 */
	
	/**
	 * 初始化界面
	 */
	private void initHeaderViewAndFooterViewAndListView(Context context){
		setOrientation(LinearLayout.VERTICAL);
		
		mContext = context;
		
		/*
		 * 自定义头部文件
		 * 放在这里是因为考虑到很多界面都需要使用
		 * 如果要修改,和它相关的设置都要更改
		 */
		mEnablePullDown = true;
		mHeaderView = LayoutInflater.from(context).inflate(R.layout.pulldown_header, null);
		mHeaderViewParams = new LayoutParams(android.view.ViewGroup.LayoutParams.FILL_PARENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
		mHeaderIncremental = mDefaultHeaderViewHeight;
		addView(mHeaderView, 0, mHeaderViewParams);
		
		mDefaultHeaderViewHeight = getResources().getDimensionPixelSize(R.dimen.pulldown_headerview_height);
		mMoveDeviation = getResources().getDimensionPixelSize(R.dimen.pulldown_move_deviation);
		mHeaderTextView = (TextView) mHeaderView.findViewById(R.id.pulldown_header_text);
		mHeaderArrowView = (ImageView) mHeaderView.findViewById(R.id.pulldown_header_arrow);
		mHeaderViewDateView = (TextView) mHeaderView.findViewById(R.id.pulldown_header_date);
		mHeaderLoadingView = mHeaderView.findViewById(R.id.pulldown_header_loading);
		
		// 注意,图片旋转之后,再执行旋转,坐标会重新开始计算
		mRotateOTo180Animation = new RotateAnimation(0, 180, 
				Animation.RELATIVE_TO_SELF, 0.5f, 
				Animation.RELATIVE_TO_SELF, 0.5f);
		mRotateOTo180Animation.setDuration(250);
		mRotateOTo180Animation.setFillAfter(true);
		
		mRotate180To0Animation = new RotateAnimation(180, 0, 
				Animation.RELATIVE_TO_SELF, 0.5f, 
				Animation.RELATIVE_TO_SELF, 0.5f);
		mRotate180To0Animation.setDuration(250);
		mRotate180To0Animation.setFillAfter(true);
		
		
		/*
		 * 自定义脚部文件
		 */
		mEnableLoadMore = true;
		mFooterView = LayoutInflater.from(mContext).inflate(R.layout.pulldown_footer, null);
		mFooterView.setVisibility(View.GONE);
		mFooterTextView = (TextView) mFooterView.findViewById(R.id.pulldown_footer_text);
		mFooterLoadingView = mFooterView.findViewById(R.id.pulldown_footer_loading);
		mFooterView.setOnClickListener(new OnClickListener() {
			@Override
			public void onClick(View v) {
				if (mIsNoMoreData || !mIsDidLoad || (state & STATE_LOADING_MORE) == STATE_LOADING_MORE) return;
				ScrollOverListView listView = mListView;
				if (listView.getCount() - listView.getHeaderViewsCount() - listView.getFooterViewsCount() > 0) {
					state |= STATE_LOADING_MORE;
					updateFooter();
					mOnPullDownListener.onLoadMore();
				}
			}
		});
		
		
		/*
		 * ScrollOverListView 同样是考虑到基本都要使用,所以放在这里
		 * 同时因为,需要它的监听事件
		 */
		mListView = new ScrollOverListView(context);
		mListView.setFooterDividersEnabled(false);
		mListView.setId(android.R.id.list);
		mListView.addFooterView(mFooterView);
		mListView.setOnScrollOverListener(this);
		mListView.setOnScrollListener(this);
		
		// 因为2.3之后的某些ListView控件自己实现了pull阴影动画效果
		// 所以我们在这里屏蔽他们
		try {
			Method method = AbsListView.class.getDeclaredMethod("setOverScrollMode", int.class);
			method.setAccessible(true);
			method.invoke(mListView, 2);//View.OVER_SCROLL_NEVER
		} catch (Exception e) {
			e.printStackTrace();
		}
		addView(mListView, android.view.ViewGroup.LayoutParams.FILL_PARENT, android.view.ViewGroup.LayoutParams.FILL_PARENT);

		// set ListView animation
		/*
		AnimationSet set = new AnimationSet(true);

        Animation animation = new AlphaAnimation(0.0f, 1.0f);
        animation.setDuration(50);
        set.addAnimation(animation);

        animation = new TranslateAnimation(
            Animation.RELATIVE_TO_SELF, 0.0f,Animation.RELATIVE_TO_SELF, 0.0f,
            Animation.RELATIVE_TO_SELF, -1.0f,Animation.RELATIVE_TO_SELF, 0.0f
        );
        animation.setDuration(100);
        set.addAnimation(animation);

        LayoutAnimationController controller = new LayoutAnimationController(set, 0.5f);
        mListView.setLayoutAnimation(controller);
		*/
		
		// 空的listener
		mOnPullDownListener = new OnPullDownListener() {
			@Override
			public void onRefresh() {}
			@Override
			public void onLoadMore() {}
		};
	}
	
	/**
	 * 去掉底部文件
	 */
	private void removeFooter(){
		if(mListView.getFooterViewsCount() > 0 && mListView != null && mFooterView != null){
			mListView.removeFooterView(mFooterView);
		}
	}
	
	/**
	 * 更新头部文件</br>
	 * 如果刷新中显示Loading,
	 * 如果拖动中,显示拖动效果,
	 * 否则显示更新时间。
	 */
	private void updateHeader() {
		if ((state & STATE_REFRESHING) == STATE_REFRESHING) {
			mHeaderArrowView.clearAnimation();
			mHeaderArrowView.setVisibility(View.GONE);
			mHeaderLoadingView.setVisibility(View.VISIBLE);
			mHeaderTextView.setText("正在刷新中...");
		} else if ((state & STATE_DRAGING) == STATE_DRAGING) {
			if(mHeaderViewParams.height >= mDefaultHeaderViewHeight){
				if(mHeaderViewState == HEADER_VIEW_STATE_OVER_HEIGHT) return;
				mHeaderArrowView.setVisibility(View.VISIBLE);
				mHeaderLoadingView.setVisibility(View.GONE);
				mHeaderViewDateView.setVisibility(View.VISIBLE);
				mHeaderViewState = HEADER_VIEW_STATE_OVER_HEIGHT;
				mHeaderTextView.setText("松开可以刷新");
				mHeaderArrowView.startAnimation(mRotateOTo180Animation);
			}else{
				if(mHeaderViewState == HEADER_VIEW_STATE_NOT_OVER_HEIGHT
						|| mHeaderViewState == HEADER_VIEW_STATE_IDLE) return;
				mHeaderArrowView.setVisibility(View.VISIBLE);
				mHeaderLoadingView.setVisibility(View.GONE);
				mHeaderViewDateView.setVisibility(View.VISIBLE);
				mHeaderViewState = HEADER_VIEW_STATE_NOT_OVER_HEIGHT;
				mHeaderTextView.setText("下拉可以刷新");
				mHeaderArrowView.startAnimation(mRotate180To0Animation);
			}
		} else {
			mHeaderLoadingView.setVisibility(View.GONE);
			mHeaderViewDateView.setVisibility(View.VISIBLE);
			mHeaderArrowView.setVisibility(View.VISIBLE);
			mHeaderTextView.setText("下拉可以刷新");
			mHeaderViewDateView.setText("更新于:" + dateFormat.format(new Date(System.currentTimeMillis())));
		}
	}
	
	/** 
	 * 更新脚部文件</br>
	 * 正常显示"更多",
	 * 如果没有更多的数据就显示"已加载完全部",
	 * 如果加载中就显示"加载中..."。
	 */
	private void updateFooter() {
		if (!mEnableLoadMore) return;
		
		if (mIsNoMoreData) {
			mFooterTextView.setText("已加载完全部");
			mFooterLoadingView.setVisibility(View.GONE);
		} else if ((state & STATE_LOADING_MORE) == STATE_LOADING_MORE) {
			mFooterTextView.setText("加载更多中...");
			mFooterLoadingView.setVisibility(View.VISIBLE);
		} else {
			mFooterTextView.setText("更多");
			mFooterLoadingView.setVisibility(View.GONE);
		}
	}
	
	private void setHeaderHeight(final int height){
		mHeaderIncremental = height;
		mHeaderViewParams.height = height;
		mHeaderView.setLayoutParams(mHeaderViewParams);
	}

	/**
	 * 自动隐藏动画
	 */
	class HideHeaderViewTask extends TimerTask{
		@Override
		public void run() {
			if((state & STATE_MOTION_DOWN) == STATE_MOTION_DOWN) {
				cancel();
				return;
			}
			mHeaderIncremental -= AUTO_INCREMENTAL;
			if(mHeaderIncremental > 0){
				mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);
			}else{
				mHeaderIncremental = 0;
				mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);
				cancel();
			}
		}
	}
	
	/**
	 * 自动显示动画
	 */
	class ShowHeaderViewTask extends TimerTask{

		@Override
		public void run() {
			if((state & STATE_MOTION_DOWN) == STATE_MOTION_DOWN) {
				cancel();
				return;
			}
			mHeaderIncremental -= AUTO_INCREMENTAL;
			if(mHeaderIncremental > mDefaultHeaderViewHeight){
				mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);
			}else{
				mHeaderIncremental = mDefaultHeaderViewHeight;
				mUIHandler.sendEmptyMessage(WHAT_SET_HEADER_HEIGHT);
				if (mIsDidLoad && (state & STATE_REFRESHING) != STATE_REFRESHING) {
					state |= STATE_REFRESHING;
					mUIHandler.post(new Runnable() {
						
						@Override
						public void run() {
							// 要清除掉动画,否则无法隐藏
							updateHeader();
							mHeaderArrowView.clearAnimation();
							mHeaderArrowView.setVisibility(View.INVISIBLE);
							mHeaderLoadingView.setVisibility(View.VISIBLE);
							mOnPullDownListener.onRefresh();
						}
					});
				}
				cancel();
			}
		}
	}


	private Handler mUIHandler = new Handler(){

		@Override
		public void handleMessage(Message msg) {
			switch (msg.what) {
				case WHAT_SET_HEADER_HEIGHT :{
					setHeaderHeight(mHeaderIncremental);
					return;
				}
			}
		}
		
	};
	
	/**
	 * 当刷新完数据时,手动调用onIdle事件
	 */
	private void doListViewIdleActionOnDataDidLoad(){
		new Handler().postDelayed(new Runnable() {
			
			@Override
			public void run() {
				if(mOnListViewIdleListener != null){
					// 不需要考虑FooterView
					final int firstVisiblePosition = mListView.getFirstVisiblePosition();
					final int childCount = mListView.getChildCount(); 
					//Log.d(TAG, "[doListViewIdleActionOnDataDidLoad] firstVisiblePosition:" + firstVisiblePosition + " childCount:" + childCount);
					mOnListViewIdleListener.onIdle(firstVisiblePosition, childCount);
				}
			}
		}, 0);
	}
	
	/**
	 * 条目是否填满整个屏幕
	 */
	private boolean isFillScreenItem(){
		final int firstVisiblePosition = mListView.getFirstVisiblePosition();
		final int lastVisiblePosition = mListView.getLastVisiblePosition() - mListView.getFooterViewsCount();
		final int visibleItemCount = lastVisiblePosition - firstVisiblePosition + 1;
		final int totalItemCount = mListView.getCount() - mListView.getFooterViewsCount();
		
		if(visibleItemCount < totalItemCount) return true;
		return false;
	}
	
	@Override
	public boolean onInterceptTouchEvent(MotionEvent ev) {
		if (ev.getAction() == MotionEvent.ACTION_DOWN) {
			state |= STATE_MOTION_DOWN;
			mIsPullUpDone = false;
			mMotionDownLastY = ev.getRawY();
			Log.d(TAG, "pulldownview.onIntercept:" + mMotionDownLastY);
		}
		return super.onInterceptTouchEvent(ev);
	}
	
	/*
	 * ==================================
	 * 实现 OnScrollOverListener接口
	 * 
	 * 
	 * ==================================
	 */

	@Override
	public boolean onListViewTopAndPullDown(MotionEvent event, int delta) {
		// 因为某些ListView控件自己已经添加了下拉效果
		// 所以我们必须返回true,控制ListView不会滚动,达到屏蔽的效果
		if ((state & STATE_REFRESHING) == STATE_REFRESHING || !mEnablePullDown || !mIsDidLoad) {
			return true;
		}
		
		if (mListView.getCount() - mListView.getFooterViewsCount() == 0) {
			return true;
		}
		
		// 如果开始按下到滑动距离不超过误差值,则不滑动
		if(mHeaderViewParams.height <= 0){
			final int absMotionY = (int) Math.abs(event.getRawY() - mMotionDownLastY);
			if(absMotionY < mMoveDeviation) {
				return true;
			}
		}
		
		int absDelta = Math.abs(delta);
		final int i = (int) Math.ceil((double)absDelta / 2);
		mHeaderIncremental += i;
		if(mHeaderIncremental >= 0){ // && mIncremental <= mMaxHeight
			setHeaderHeight(mHeaderIncremental);
			updateHeader();
		}
		return true;
	}

	@Override
	public boolean onListViewBottomAndPullUp(MotionEvent event, int delta) {
		if((state & STATE_LOADING_MORE) == STATE_LOADING_MORE
				|| !mIsDidLoad || !mEnableAutoFetchMore || mIsNoMoreData) return false;
		ScrollOverListView listView = mListView;
		if (listView.getCount() - listView.getHeaderViewsCount() - listView.getFooterViewsCount() > 0) {
			state |= STATE_LOADING_MORE;
			updateFooter();
			mOnPullDownListener.onLoadMore();
		}
		return true;
	}

	@Override
	public boolean onMotionDown(MotionEvent ev) {
		return false;
	}

	@Override
	public boolean onMotionMove(MotionEvent ev, int delta) {
		state |= STATE_DRAGING;
		//当头部文件回推消失的时候,不允许滚动
		if(mIsPullUpDone) return true;
		
		// onTopDown在顶部,并向上回推和onTopUp相对
		if(mHeaderViewParams.height > 0 && delta < 0){
			final int absDelta = Math.abs(delta);
			final int i = (int) Math.ceil((double)absDelta / 2);
			
			mHeaderIncremental -= i;
			if(mHeaderIncremental > 0){
				setHeaderHeight(mHeaderIncremental);
				updateHeader();
			}else{
				mHeaderViewState = HEADER_VIEW_STATE_IDLE;
				mHeaderIncremental = 0;
				setHeaderHeight(mHeaderIncremental);
				mIsPullUpDone = true;
			}
			return true;
		}
		return false;
	}

	@Override
	public boolean onMotionUp(MotionEvent ev) {
		state &= ~STATE_DRAGING;
		state &= ~STATE_MOTION_DOWN;
		// 避免和点击事件冲突
		if(mHeaderViewParams.height > 0 || mIsPullUpDone){
			
			// 判断头文件拉动的距离与设定的高度,小了就隐藏,多了就固定高度
			int x = mHeaderIncremental - mDefaultHeaderViewHeight;
			Timer timer = new Timer(true);
			if(x < 0){
				timer.scheduleAtFixedRate(new HideHeaderViewTask(), 0, 10);
			}else{
				timer.scheduleAtFixedRate(new ShowHeaderViewTask(), 0, 10);
			}
			return true;
		}
		return false;//TODO
	}
	
	/*
	 * =====================================
	 * 监听ListView停止事件
	 * =====================================
	 */
	
	private OnListViewIdleListener mOnListViewIdleListener;
	
	public interface OnListViewIdleListener {
		void onIdle(int startIndex, int count);
	}
	
	public void setOnListViewIdleListener(OnListViewIdleListener listener){
		if(mListView != null){
			mOnListViewIdleListener = listener;
		}
	}

	@Override
	public void onScrollStateChanged(AbsListView view, int scrollState) {
		if(scrollState == SCROLL_STATE_IDLE){
			if (DEBUG) Log.d(TAG, "IDLE");
			if(mOnListViewIdleListener != null){
				int count = mListView.getChildCount();
				final int childEndIndex = mStartIndex + mListView.getChildCount() -1;
				final int listEndIndex = mListView.getCount() -1;
				if(childEndIndex == listEndIndex){
					count -= mListView.getFooterViewsCount();
				}
				mOnListViewIdleListener.onIdle(mStartIndex, count);
			}
		}
		switch (scrollState) {
		case SCROLL_STATE_FLING:
			if (DEBUG) Log.d(TAG, "FLING");
			break;
		case SCROLL_STATE_TOUCH_SCROLL:
			if (DEBUG) Log.d(TAG, "SCROLL");
		default:
			break;
		}
	}

	@Override
	public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
		if (DEBUG) Log.d(TAG, "onScroll");
		mStartIndex = firstVisibleItem;
		
		// 因为三星I91002.3版本如果设置over_scroll_never会出现回弹问题,所以在这里处理一下
		// 更新12-9-9 2:08:晕死,测试三星2.3.3版本如果反射使用这些属性会报错。
		/*
		final ScrollOverListView localListView = this.mListView;		
		final boolean hasItem = localListView.getCount() > 0;

		try {
			if (overScrollModeField == null) return;
			final Integer mode = (Integer) overScrollModeField.get(localListView);
			
			if (firstVisibleItem <= 0 && hasItem) {
				if (mode != View.OVER_SCROLL_NEVER) {
					if (DEBUG) Log.w(TAG, "set over scroll never");
					overScrollModeField.set(localListView, View.OVER_SCROLL_NEVER);
				}
			} else if (firstVisibleItem + visibleItemCount >= totalItemCount && hasItem) {
				if (mode != View.OVER_SCROLL_ALWAYS) {
					if (DEBUG) Log.w(TAG, "set over scroll always");
					overScrollModeField.set(localListView, View.OVER_SCROLL_ALWAYS);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		*/
	}

}


三、Demo界面
package com.example.pulldownview;

import java.util.ArrayList;
import java.util.List;

import android.app.Activity;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.TextView;

import com.example.pulldownview.PullDownView.OnPullDownListener;

public class MainActivity extends Activity {

    private PullDownView pullDownView;
	private ScrollOverListView listView;
	private MyAdapter adapter;
	private List<String> arrays;
	private LayoutInflater inflater;

	@Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        inflater = getLayoutInflater();
        
        pullDownView = (PullDownView) findViewById(R.id.pulldownview);
        pullDownView.enableAutoFetchMore(true, 0);
        listView = pullDownView.getListView();
        adapter = new MyAdapter();
        listView.setAdapter(adapter);
        
        initArrays(new Handler(){
        	@Override
        	public void handleMessage(Message msg) {
        		arrays = (List<String>) msg.obj;
        		adapter.notifyDataSetChanged();
        		pullDownView.notifyDidDataLoad(false);
        	}
        });
        
        pullDownView.setOnPullDownListener(new OnPullDownListener() {
			
			@Override
			public void onRefresh() {
				getNewString(new Handler(){
					@Override
					public void handleMessage(Message msg) {
						arrays.add(0, (String) msg.obj);
						adapter.notifyDataSetChanged();
						pullDownView.notifyDidRefresh(arrays.isEmpty());
					}
				});
			}
			
			@Override
			public void onLoadMore() {
				getNewString(new Handler(){
					@Override
					public void handleMessage(Message msg) {
						arrays.add((String) msg.obj);
						adapter.notifyDataSetChanged();
						pullDownView.notifyDidLoadMore(arrays.isEmpty());
					}
				});
			}
		});
    }
	
	private void initArrays(final Handler handler) {
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				try {
					Thread.sleep(3000);
				} catch (Exception e) {
					Thread.interrupted();
					e.printStackTrace();
				}
				
				List<String> as = new ArrayList<String>();
				as.add("first");
				as.add("second");
				as.add("third");
				as.add("four");
				as.add("five");
				as.add("first");
				as.add("second");
				as.add("third");
				as.add("four");
				as.add("five");
				as.add("first");
				as.add("second");
				as.add("third");
				as.add("four");
				as.add("five");
				as.add("first");
				as.add("second");
				as.add("third");
				as.add("four");
				as.add("five");
				as.add("first");
				as.add("second");
				as.add("third");
				as.add("four");
				as.add("five");
				
				handler.obtainMessage(0, as).sendToTarget();
			}
		}).start();
	}
	
	private void getNewString(final Handler handler) {
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				try {
					Thread.sleep(2000);
				} catch (Exception e) {
					Thread.interrupted();
					e.printStackTrace();
				}
				handler.obtainMessage(0, "New Text " + System.currentTimeMillis()).sendToTarget();
			}
		}).start();
	}

	
	private class MyAdapter extends BaseAdapter {

		@Override
		public int getCount() {
			return arrays == null ? 0 : arrays.size();
		}

		@Override
		public Object getItem(int position) {
			return null;
		}

		@Override
		public long getItemId(int position) {
			return 0;
		}

		@Override
		public View getView(int position, View convertView, ViewGroup parent) {
			ViewHolder holder;
			if (convertView == null) {
				holder = new ViewHolder();
				convertView = inflater.inflate(R.layout.item_list, null);
				holder.textView = (TextView) convertView.findViewById(R.id.text);
				convertView.setTag(holder);
			} else {
				holder = (ViewHolder) convertView.getTag();
			}
			holder.textView.setText(arrays.get(position));
			
			return convertView;
		}
	}
	
	private static class ViewHolder {
		TextView textView;
	}
}

分享到:
评论
3 楼 mark20111213 2015-04-21  
很棒
2 楼 hackmyth 2013-08-27  
感谢分享!!哇哈哈哈
1 楼 nbzxforever 2012-09-13  
感谢分享!

相关推荐

Global site tag (gtag.js) - Google Analytics