天天看點

Android ListView 下拉重新整理、上拉加載

[size=medium]之前在用ListView做下拉重新整理和上拉加載的時候一直都是在網上找的執行個體,

但效果都不是太好,然後自己根據執行個體的思路,自己梳理了一下,自己也寫了一把,感覺也不是太好,在此做下記錄吧,希望對初入ANDROID開發的新手們有所幫助,直接上代碼吧[/size]

package cn.zan.control.view;

import java.util.Date;

import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.LinearInterpolator;
import android.view.animation.RotateAnimation;
import android.widget.AbsListView;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.TextView;
import cn.zan.zan_society.R;

/**
 * Gening 2013-10-31
 **/
public class CustomListView extends ListView {

	private static final int DONE = 0;
	private static final int PULL_TO_REFRESH = 1;
	private static final int RELEASE_TO_REFRESH = 2;
	private static final int REFRESHING = 3;
	private static final float RATIO = 3;

	private int state;                         // 目前下拉重新整理的狀态

	private int firstVisibleIndex;             // ListView 中第一個可以看見的Item下标

	/***************************** ListView 頭部 View 控件 ***************************/
	private View headView;                      // ListView 頭部 View
	private ImageView headArrow;                // ListView 頭部 View的箭頭
	private ProgressBar progressBar;            // ListView 頭部 View的讀取轉圈
	private TextView headTitle;                 // ListView 頭部 View裡的文字
	private TextView headLastUpdate;            // ListView 頭部 View裡的文字,最後更新時間
	private int headContentHeight;              // ListView 頭部 View的高度
	private Animation animation;
	private Animation reverseAnimation;

	private boolean isRefreshable;
	private boolean isRecored = false;          // 用來記錄第一次按下坐标點,在整個滑動的過程中 隻記錄一次

	private float startY;
	private boolean isBack = false;

	private boolean isFootLoading = false;      // 正在加載底部資料辨別
	private boolean hasFoot = false;            // 是否有了底部 View(FootView)
	private int lastPos;                        // 最後一個可見的item的位置
	private int count;                          // ListView Item總數,注意不是目前可見的item總數

	public CustomListView(Context context, AttributeSet attrs) {
		super(context, attrs);
		if (isInEditMode()) { return; }
		init(context);
	}

	private void init(Context context) {
		// listview 設定滑動時緩沖背景色
		setCacheColorHint(0x00000000);

		headView = View.inflate(context, R.layout.head, null);
		headArrow = (ImageView) headView.findViewById(R.id.head_arrow);
		progressBar = (ProgressBar) headView.findViewById(R.id.progressbar);
		headTitle = (TextView) headView.findViewById(R.id.head_title);
		headLastUpdate = (TextView) headView.findViewById(R.id.head_last_update);

		headArrow.setMinimumWidth(50);
		headArrow.setMinimumHeight(70);

		MeasureView(headView);

		headContentHeight = headView.getMeasuredHeight();

		headView.setPadding(0, -1 * headContentHeight, 0, 0);
		addHeaderView(headView); // 為 ListView加入頂部 View
		this.setOnScrollListener(custom_listview_onscroll_lis);

		animation = new RotateAnimation(-180, 0, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
		animation.setDuration(250);
		animation.setFillAfter(true); // 設定動畫結束時,停留在動畫結束位置 (保留動畫效果)
		animation.setInterpolator(new LinearInterpolator()); // 勻速變化

		reverseAnimation = new RotateAnimation(0, -180, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
		reverseAnimation.setDuration(200);
		reverseAnimation.setFillAfter(true);// 設定動畫結束時,停留在動畫結束位置 (保留動畫效果)
		reverseAnimation.setInterpolator(new LinearInterpolator());// 勻速變化

		// 設定目前headView的狀态
		state = DONE;

		// 設定目前下拉重新整理是否可用
		isRefreshable = false;
	}

	/** 測量headView的 寬高 **/
	private void MeasureView(View child) {
		ViewGroup.LayoutParams lp = child.getLayoutParams();

		if (null == lp) {
			lp = new ViewGroup.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT);
		}

		int measureChildWidth = ViewGroup.getChildMeasureSpec(0, 0, lp.width);
		int measureChildHeight;

		if (lp.height > 0) {
			measureChildHeight = MeasureSpec.makeMeasureSpec(lp.height, MeasureSpec.EXACTLY);
		} else {
			measureChildHeight = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
		}
		child.measure(measureChildWidth, measureChildHeight);
	}

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		switch (event.getAction()) {
		case MotionEvent.ACTION_DOWN:
			if (isRefreshable) {
				if (firstVisibleIndex == 0 && !isRecored) {
					startY = event.getY();
					isRecored = true;
				}
			}
			break;
		case MotionEvent.ACTION_MOVE:
			if (isRefreshable) {
				float tempY = event.getY();
				if (firstVisibleIndex == 0 && !isRecored) {
					startY = tempY;
					isRecored = true;
				}
				if (isRecored) {
					if (tempY - startY < 0) {
						break;
					}
					if (state != REFRESHING) {
						if (state == PULL_TO_REFRESH) {
							if ((tempY - startY) / RATIO >= headContentHeight && (tempY - startY) > 0) {
								// 向下拉了 從下拉重新整理的狀态 來到 松開重新整理的狀态
								state = RELEASE_TO_REFRESH;
								changeHeadViewOfState();
							} else if ((tempY - startY) <= 0) {
								// 向上推了 從下拉重新整理的狀态 來到 重新整理完成的狀态
								state = DONE;
								changeHeadViewOfState();
							}
						} else if (state == RELEASE_TO_REFRESH) {
							// 向上推了 還沒有完全将HEADVIEW 隐藏掉(可以看到一部分)
							if ((tempY - startY) / RATIO < headContentHeight && (tempY - startY) > 0) {
								// 從松開重新整理的狀态 來到 下拉重新整理的狀态
								state = PULL_TO_REFRESH;
								changeHeadViewOfState();
								isBack = true;
							} else if ((tempY - startY) <= 0) {
								// 向上推了 一下子推到了最上面 從松開重新整理的狀态 來到 重新整理完成的狀态 (資料不重新整理的)
								state = DONE;
								changeHeadViewOfState();
							}
						} else if (state == DONE) {
							// 重新整理完成的狀态 來到 下拉重新整理的狀态
							if ((tempY - startY) > 0) {
								state = PULL_TO_REFRESH;
								changeHeadViewOfState();
							}
						}
						if (state == PULL_TO_REFRESH)
							headView.setPadding(0, (int) ((tempY - startY) / RATIO - headContentHeight), 0, 0);
						if (state == RELEASE_TO_REFRESH)
							headView.setPadding(0, (int) ((tempY - startY) / RATIO - headContentHeight), 0, 0);
					}
				}
			}
			break;
		case MotionEvent.ACTION_UP:
			if (isRefreshable) {
				if (state != REFRESHING) {
					if (state == PULL_TO_REFRESH) {
						// 松手
						state = DONE;
						changeHeadViewOfState();
					} else if (state == RELEASE_TO_REFRESH) {
						// 松手
						state = REFRESHING;
						changeHeadViewOfState();
						// 執行資料重新整理方法
						onRefresh();
					}
				}
				isRecored = false;
				isBack = false;
			}
			break;
		}
		return super.onTouchEvent(event);
	}

	/** 改變下拉重新整理時,頭部控件顯示樣式 **/
	private void changeHeadViewOfState() {
		switch (state) {
		case PULL_TO_REFRESH:
			headArrow.setVisibility(View.VISIBLE);
			progressBar.setVisibility(View.GONE);
			headTitle.setVisibility(View.VISIBLE);
			headLastUpdate.setVisibility(View.VISIBLE);
			headArrow.clearAnimation();
			headTitle.setText("下拉可以重新整理");
			// 由 松開重新整理 到 下拉重新整理
			if (isBack) {
				headArrow.startAnimation(animation);
				isBack = false;
			}
			break;
		case RELEASE_TO_REFRESH:
			headArrow.setVisibility(View.VISIBLE);
			progressBar.setVisibility(View.GONE);
			headTitle.setVisibility(View.VISIBLE);
			headLastUpdate.setVisibility(View.VISIBLE);
			headArrow.clearAnimation();
			headArrow.startAnimation(reverseAnimation);
			headTitle.setText("松開可以重新整理");
			break;
		case REFRESHING:
			headArrow.setVisibility(View.GONE);
			progressBar.setVisibility(View.VISIBLE);
			headTitle.setVisibility(View.VISIBLE);
			headLastUpdate.setVisibility(View.VISIBLE);
			headArrow.clearAnimation();
			headTitle.setText("正在重新整理...");
			headView.setPadding(0, 0, 0, 0);
			break;
		case DONE:
			headArrow.setVisibility(View.VISIBLE);
			progressBar.setVisibility(View.GONE);
			headTitle.setVisibility(View.VISIBLE);
			headLastUpdate.setVisibility(View.VISIBLE);
			headArrow.clearAnimation();
			headTitle.setText("下拉可以重新整理");
			headView.setPadding(0, -1 * headContentHeight, 0, 0);
			break;
		}
	}

	/** ListView 監聽事件 **/
	private OnScrollListener custom_listview_onscroll_lis = new OnScrollListener() {
		@Override
		public void onScrollStateChanged(AbsListView view, int scrollState) {
			if (!isFootLoading) {
				// 沒有滾動
				if (hasFoot && scrollState == SCROLL_STATE_IDLE) {
					isFootLoading = true;
					onFootLoading();
				}
			}
		}

		@Override
		public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
			firstVisibleIndex = firstVisibleItem;
			lastPos = getLastVisiblePosition();
			count = totalItemCount;

			// 因為剛進入的時候,lastPos=-1,count=0,這個時候不能讓它執行onAddFoot方法
			if (lastPos == count - 1 && !hasFoot && lastPos != -1) {
				hasFoot = true;
				onAddFoot();
			}
		}
	};

	/** 下拉重新整理監聽 **/
	private OnRefreshListner refreshListner; // 重新整理監聽器

	/** 設定下拉重新整理監聽器 **/
	public void setOnRefreshListner(OnRefreshListner listener) {
		isRefreshable = true;
		refreshListner = listener;
	} 

	/** 執行下拉重新整理操作 **/
	private void onRefresh() {
		if (refreshListner != null) {
			refreshListner.onRefresh();
		}
	} 

	/** 添加底部View(FootView)監聽器 **/
	public OnAddFootListener onAddFootListener;

	/** 設定添加Foot監聽器 **/
	public void setOnAddFootListener(OnAddFootListener addFootListener) {
		onAddFootListener = addFootListener;
	} 

	/** 執行添加Foot **/
	public void onAddFoot() {
		if (onAddFootListener != null && hasFoot) {
			onAddFootListener.addFoot();
		}
	} 

	/** 上拉加載,監聽器 **/
	public OnFootLoadingListener footLoadingListener;

	public void setOnFootLoadingListener(OnFootLoadingListener footLoading) {
		footLoadingListener = footLoading;
	}

	/** 執行底部加載 **/
	public void onFootLoading() {
		if (footLoadingListener != null && isFootLoading) {
			footLoadingListener.onFootLoading();
		}
	} 

	/********************************************************************** 監聽器 **************************************************************************/
	/** 下拉重新整理監聽器 **/
	public interface OnRefreshListner {
		// 下拉重新整理的時候,在這裡執行擷取資料的過程
		void onRefresh();
	}

	/** 添加Foot的監聽器 **/
	public interface OnAddFootListener {
		// 這裡是使用者addFootView的操作
		void addFoot();
	}

	/** 上拉加載監聽器 **/
	public interface OnFootLoadingListener {
		// 上拉加載的時候,在這裡行背景擷取資料的過程
		void onFootLoading();
	}

	/******************************************************************** 對外調用方法 *****************************************************************/
	/** 底部資料加載完成,使用者需要加入一個removeFootView的操作 **/
	public void onFootLoadingComplete() {
		hasFoot = false;
		isFootLoading = false;
	}

	/** 上拉重新整理完成時 所執行的操作,更改狀态,隐藏head **/
	public void onRefreshComplete() {
		state = DONE;
		changeHeadViewOfState();
		headLastUpdate.setText("最後重新整理時間: " + new Date().toLocaleString());
	}

	@Override
	public void setAdapter(ListAdapter adapter) {
		headLastUpdate.setText("最後重新整理時間: " + new Date().toLocaleString());
		super.setAdapter(adapter);
	}
}
           

[size=large]在外部使用的時候,

需使用下拉重新整理,直接實作CustomListView的OnRefreshListenner接口

需使用上拉加載,先實作Custom的setOnAddFootListener接口為ListView賦上想要顯示的布局,然後再實作OnFootLoadingListener接口執行加載時需要做的請求、資料解析及ListView資料源重新整理。最後需調用CustomListView.OnFootLoadingComplete()回複初始狀态 和 CustomListView.removeFooterView()移除FootViewW布局。

大概實作就是這樣,在此因為時間的原因沒有寫上實作的思路,有時間補上。但是此執行個體有明顯的不足之處。

1. 下拉重新整理與上拉加載的效果太突兀,還有待優化。

2. 下拉重新整理與上拉加載的touch事件實作上區分的還不是很清晰明确,還有待優化。[/size]

繼續閱讀