1. 程式人生 > >android ListView的上部下拉重新整理下部點選載入更多具體實現及拓展

android ListView的上部下拉重新整理下部點選載入更多具體實現及拓展

這次就不上圖了,例子太多太多了,想必大家都見過.這個功能的實現,簡直是開發者必備的.

我也不過多介紹了,網上詳細介紹的部落格太多太多了,若想深入瞭解,請參考網上其他博文.

在這裡,我只是按照自己的理解,模擬實現了一個,順便程式碼貢獻出來.

我對之詳細標明的註釋,想必如果不懂的同學們,看註釋也應該明白,前提是,你要耐心看,因為程式碼有點多,但是我整理過了,還算清晰.

詳細程式碼:

package com.jj.drag;

import android.content.Context;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
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.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;

/***
 * 自定義拖拉ListView
 * 
 * @author zhangjia
 * 
 */
public class DragListView extends ListView implements OnScrollListener,
		OnClickListener {
	// 拖拉ListView列舉所有狀態
	private enum DListViewState {
		LV_NORMAL, // 普通狀態
		LV_PULL_REFRESH, // 下拉狀態(為超過mHeadViewHeight)
		LV_RELEASE_REFRESH, // 鬆開可重新整理狀態(超過mHeadViewHeight)
		LV_LOADING;// 載入狀態
	}

	// 點選載入更多列舉所有狀態
	private enum DListViewLoadingMore {
		LV_NORMAL, // 普通狀態
		LV_LOADING, // 載入狀態
		LV_OVER; // 結束狀態
	}

	private View mHeadView;// 頭部headView
	private TextView mRefreshTextview; // 重新整理msg(mHeadView)
	private TextView mLastUpdateTextView;// 更新事件(mHeadView)
	private ImageView mArrowImageView;// 下拉圖示(mHeadView)
	private ProgressBar mHeadProgressBar;// 重新整理進度體(mHeadView)

	private int mHeadViewWidth; // headView的寬(mHeadView)
	private int mHeadViewHeight;// headView的高(mHeadView)

	private View mFootView;// 尾部mFootView
	private View mLoadMoreView;// mFootView 的view(mFootView)
	private TextView mLoadMoreTextView;// 載入更多.(mFootView)
	private View mLoadingView;// 載入中...View(mFootView)

	private Animation animation, reverseAnimation;// 旋轉動畫,旋轉動畫之後旋轉動畫.

	private int mFirstItemIndex = -1;// 當前檢視能看到的第一個項的索引

	// 用於保證startY的值在一個完整的touch事件中只被記錄一次
	private boolean mIsRecord = false;

	private int mStartY, mMoveY;// 按下是的y座標,move時的y座標

	private DListViewState mlistViewState = DListViewState.LV_NORMAL;// 拖拉狀態.(自定義列舉)

	private DListViewLoadingMore loadingMoreState = DListViewLoadingMore.LV_NORMAL;// 載入更多預設狀態.

	private final static int RATIO = 2;// 手勢下拉距離比.

	private boolean mBack = false;// headView是否返回.

	private OnRefreshLoadingMoreListener onRefreshLoadingMoreListener;// 下拉重新整理介面(自定義)

	private boolean isScroller = true;// 是否遮蔽ListView滑動。

	public DragListView(Context context) {
		super(context, null);
		initDragListView(context);
	}

	public DragListView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initDragListView(context);
	}

	// 注入下拉重新整理介面
	public void setOnRefreshListener(
			OnRefreshLoadingMoreListener onRefreshLoadingMoreListener) {
		this.onRefreshLoadingMoreListener = onRefreshLoadingMoreListener;
	}

	/***
	 * 初始化ListView
	 */
	public void initDragListView(Context context) {

		String time = "1994.12.05";// 更新時間

		initHeadView(context, time);// 初始化該head.

		initLoadMoreView(context);// 初始化footer

		setOnScrollListener(this);// ListView滾動監聽
	}

	/***
	 * 初始話頭部HeadView
	 * 
	 * @param context
	 *            上下文
	 * @param time
	 *            上次更新時間
	 */
	public void initHeadView(Context context, String time) {
		mHeadView = LayoutInflater.from(context).inflate(R.layout.head, null);
		mArrowImageView = (ImageView) mHeadView
				.findViewById(R.id.head_arrowImageView);
		mArrowImageView.setMinimumWidth(60);

		mHeadProgressBar = (ProgressBar) mHeadView
				.findViewById(R.id.head_progressBar);

		mRefreshTextview = (TextView) mHeadView
				.findViewById(R.id.head_tipsTextView);

		mLastUpdateTextView = (TextView) mHeadView
				.findViewById(R.id.head_lastUpdatedTextView);
		// 顯示更新事件
		mLastUpdateTextView.setText("最近更新:" + time);

		measureView(mHeadView);
		// 獲取寬和高
		mHeadViewWidth = mHeadView.getMeasuredWidth();
		mHeadViewHeight = mHeadView.getMeasuredHeight();

		addHeaderView(mHeadView, null, false);// 將初始好的ListView add進拖拽ListView
		// 在這裡我們要將此headView設定到頂部不顯示位置.
		mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);

		initAnimation();// 初始化動畫
	}

	/***
	 * 初始化底部載入更多控制元件
	 */
	private void initLoadMoreView(Context context) {
		mFootView = LayoutInflater.from(context).inflate(R.layout.footer, null);

		mLoadMoreView = mFootView.findViewById(R.id.load_more_view);

		mLoadMoreTextView = (TextView) mFootView
				.findViewById(R.id.load_more_tv);

		mLoadingView = (LinearLayout) mFootView
				.findViewById(R.id.loading_layout);

		mLoadMoreView.setOnClickListener(this);

		addFooterView(mFootView);
	}

	/***
	 * 初始化動畫
	 */
	private void initAnimation() {
		// 旋轉動畫
		animation = new RotateAnimation(0, -180,
				RotateAnimation.RELATIVE_TO_SELF, 0.5f,
				RotateAnimation.RELATIVE_TO_SELF, 0.5f);
		animation.setInterpolator(new LinearInterpolator());// 勻速
		animation.setDuration(250);
		animation.setFillAfter(true);// 停留在最後狀態.
		// 反向旋轉動畫
		reverseAnimation = new RotateAnimation(-180, 0,
				RotateAnimation.RELATIVE_TO_SELF, 0.5f,
				RotateAnimation.RELATIVE_TO_SELF, 0.5f);
		reverseAnimation.setInterpolator(new LinearInterpolator());
		reverseAnimation.setDuration(250);
		reverseAnimation.setFillAfter(true);
	}

	/***
	 * 作用:測量 headView的寬和高.
	 * 
	 * @param child
	 */
	private void measureView(View child) {
		ViewGroup.LayoutParams p = child.getLayoutParams();
		if (p == null) {
			p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_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);
	}

	/***
	 * touch 事件監聽
	 */
	@Override
	public boolean onTouchEvent(MotionEvent ev) {
		switch (ev.getAction()) {
		// 按下
		case MotionEvent.ACTION_DOWN:
			doActionDown(ev);
			break;
		// 移動
		case MotionEvent.ACTION_MOVE:
			doActionMove(ev);
			break;
		// 擡起
		case MotionEvent.ACTION_UP:
			doActionUp(ev);
			break;
		default:
			break;
		}
		/***
		 * 如果是ListView本身的拉動,那麼返回true,這樣ListView不可以拖動.
		 * 如果不是ListView的拉動,那麼呼叫父類方法,這樣就可以上拉執行.
		 */
		if (isScroller) {
			return super.onTouchEvent(ev);
		} else {
			return true;
		}

	}

	/***
	 * 摁下操作
	 * 
	 * 作用:獲取摁下是的y座標
	 * 
	 * @param event
	 */
	void doActionDown(MotionEvent event) {
		if (mIsRecord == false && mFirstItemIndex == 0) {
			mStartY = (int) event.getY();
			mIsRecord = true;
		}
	}

	/***
	 * 拖拽移動操作
	 * 
	 * @param event
	 */
	void doActionMove(MotionEvent event) {
		mMoveY = (int) event.getY();// 獲取實時滑動y座標
		// 檢測是否是一次touch事件.
		if (mIsRecord == false && mFirstItemIndex == 0) {
			mStartY = (int) event.getY();
			mIsRecord = true;
		}
		/***
		 * 如果touch關閉或者正處於Loading狀態的話 return.
		 */
		if (mIsRecord == false || mlistViewState == DListViewState.LV_LOADING) {
			return;
		}
		// 向下啦headview移動距離為y移動的一半.(比較友好)
		int offset = (mMoveY - mStartY) / RATIO;

		switch (mlistViewState) {
		// 普通狀態
		case LV_NORMAL: {
			// 如果<0,則意味著上滑動.
			if (offset > 0) {
				// 設定headView的padding屬性.
				mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);
				switchViewState(DListViewState.LV_PULL_REFRESH);// 下拉狀態
			}

		}
			break;
		// 下拉狀態
		case LV_PULL_REFRESH: {
			setSelection(0);// 時時保持在頂部.
			// 設定headView的padding屬性.
			mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);
			if (offset < 0) {
				/***
				 * 要明白為什麼isScroller = false;
				 */
				isScroller = false;
				switchViewState(DListViewState.LV_NORMAL);// 普通狀態
				Log.e("jj", "isScroller=" + isScroller);
			} else if (offset > mHeadViewHeight) {// 如果下拉的offset超過headView的高度則要執行重新整理.
				switchViewState(DListViewState.LV_RELEASE_REFRESH);// 更新為可重新整理的下拉狀態.
			}
		}
			break;
		// 可重新整理狀態
		case LV_RELEASE_REFRESH: {
			setSelection(0);時時保持在頂部
			// 設定headView的padding屬性.
			mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);
			// 下拉offset>0,但是沒有超過headView的高度.那麼要goback 原裝.
			if (offset >= 0 && offset <= mHeadViewHeight) {
				mBack = true;
				switchViewState(DListViewState.LV_PULL_REFRESH);
			} else if (offset < 0) {
				switchViewState(DListViewState.LV_NORMAL);
			} else {

			}
		}
			break;
		default:
			return;
		}
		;
	}

	/***
	 * 手勢擡起操作
	 * 
	 * @param event
	 */
	public void doActionUp(MotionEvent event) {
		mIsRecord = false;// 此時的touch事件完畢,要關閉。
		isScroller = true;// ListView可以Scrooler滑動.
		mBack = false;
		// 如果下拉狀態處於loading狀態.
		if (mlistViewState == DListViewState.LV_LOADING) {
			return;
		}
		// 處理相應狀態.
		switch (mlistViewState) {
		// 普通狀態
		case LV_NORMAL:

			break;
		// 下拉狀態
		case LV_PULL_REFRESH:
			mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);
			switchViewState(mlistViewState.LV_NORMAL);
			break;
		// 重新整理狀態
		case LV_RELEASE_REFRESH:
			mHeadView.setPadding(0, 0, 0, 0);
			switchViewState(mlistViewState.LV_LOADING);
			onRefresh();// 下拉重新整理
			break;
		}

	}

	// 切換headview檢視
	private void switchViewState(DListViewState state) {

		switch (state) {
		// 普通狀態
		case LV_NORMAL: {
			mArrowImageView.clearAnimation();// 清除動畫
			mArrowImageView.setImageResource(R.drawable.arrow);
		}
			break;
		// 下拉狀態
		case LV_PULL_REFRESH: {
			mHeadProgressBar.setVisibility(View.GONE);// 隱藏進度條
			mArrowImageView.setVisibility(View.VISIBLE);// 下拉圖示
			mRefreshTextview.setText("下拉可以重新整理");
			mArrowImageView.clearAnimation();// 清除動畫

			// 是有可重新整理狀態(LV_RELEASE_REFRESH)轉為這個狀態才執行,其實就是你下拉後在上拉會執行.
			if (mBack) {
				mBack = false;
				mArrowImageView.clearAnimation();// 清除動畫
				mArrowImageView.startAnimation(reverseAnimation);// 啟動反轉動畫
			}
		}
			break;
		// 鬆開重新整理狀態
		case LV_RELEASE_REFRESH: {
			mHeadProgressBar.setVisibility(View.GONE);// 隱藏進度條
			mArrowImageView.setVisibility(View.VISIBLE);// 顯示下拉圖示
			mRefreshTextview.setText("鬆開獲取更多");
			mArrowImageView.clearAnimation();// 清除動畫
			mArrowImageView.startAnimation(animation);// 啟動動畫
		}
			break;
		// 載入狀態
		case LV_LOADING: {
			Log.e("!!!!!!!!!!!", "convert to IListViewState.LVS_LOADING");
			mHeadProgressBar.setVisibility(View.VISIBLE);
			mArrowImageView.clearAnimation();
			mArrowImageView.setVisibility(View.GONE);
			mRefreshTextview.setText("載入中...");
		}
			break;
		default:
			return;
		}
		// 切記不要忘記時時更新狀態。
		mlistViewState = state;

	}

	/***
	 * 下拉重新整理
	 */
	private void onRefresh() {
		if (onRefreshLoadingMoreListener != null) {
			onRefreshLoadingMoreListener.onRefresh();
		}
	}

	/***
	 * 下拉重新整理完畢
	 */
	public void onRefreshComplete() {
		mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);// 迴歸.
		switchViewState(mlistViewState.LV_NORMAL);//
	}

	/***
	 * 點選載入更多
	 * 
	 * @param flag
	 *            資料是否已全部載入完畢
	 */
	public void onLoadMoreComplete(boolean flag) {
		if (flag) {
			updateLoadMoreViewState(DListViewLoadingMore.LV_OVER);
		} else {
			updateLoadMoreViewState(DListViewLoadingMore.LV_NORMAL);
		}

	}

	// 更新Footview檢視
	private void updateLoadMoreViewState(DListViewLoadingMore state) {
		switch (state) {
		// 普通狀態
		case LV_NORMAL:
			mLoadingView.setVisibility(View.GONE);
			mLoadMoreTextView.setVisibility(View.VISIBLE);
			mLoadMoreTextView.setText("檢視更多");
			break;
		// 載入中狀態
		case LV_LOADING:
			mLoadingView.setVisibility(View.VISIBLE);
			mLoadMoreTextView.setVisibility(View.GONE);
			break;
		// 載入完畢狀態
		case LV_OVER:
			mLoadingView.setVisibility(View.GONE);
			mLoadMoreTextView.setVisibility(View.VISIBLE);
			mLoadMoreTextView.setText("載入完畢");
			break;
		default:
			break;
		}
		loadingMoreState = state;
	}

	/***
	 * ListView 滑動監聽
	 */
	@Override
	public void onScrollStateChanged(AbsListView view, int scrollState) {

	}

	@Override
	public void onScroll(AbsListView view, int firstVisibleItem,
			int visibleItemCount, int totalItemCount) {
		mFirstItemIndex = firstVisibleItem;
	}

	/***
	 * 底部點選事件
	 */
	@Override
	public void onClick(View v) {
		// 防止重複點選
		if (onRefreshLoadingMoreListener != null
				&& loadingMoreState == DListViewLoadingMore.LV_NORMAL) {
			updateLoadMoreViewState(DListViewLoadingMore.LV_LOADING);
			onRefreshLoadingMoreListener.onLoadMore();// 對外提供方法載入更多.
		}

	}

	/***
	 * 自定義介面
	 */
	public interface OnRefreshLoadingMoreListener {
		/***
		 * // 下拉重新整理執行
		 */
		void onRefresh();

		/***
		 * 點選載入更多
		 */
		void onLoadMore();
	}

}


上面就是全部程式碼,其實重要的是明白理解,這樣我們還可以進行拓展.

具體應用:(只需要這樣引用即可.)

    <com.jj.drag.DragListView
        android:id="@+id/dlv_main"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:cacheColorHint="#00000000" />

在Activity中的呼叫,相比大家都清楚,開個非同步或執行緒進行載入資料,這裡我簡單說一下非同步使用,執行緒同理.

程式碼如下:

/***
	 * 執行類 非同步
	 * 
	 * @author zhangjia
	 * 
	 */
	class MyAsyncTask extends AsyncTask<Void, Void, Void> {
		private Context context;
		private int index;// 用於判斷是下拉重新整理還是點選載入更多

		public MyAsyncTask(Context context, int index) {
			this.context = context;
			this.index = index;
		}

		@Override
		protected Void doInBackground(Void... params) {
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e1) {
				e1.printStackTrace();
			}

			return null;
		}

		@Override
		protected void onPreExecute() {
			super.onPreExecute();
		}

		@Override
		protected void onPostExecute(Void result) {
			super.onPostExecute(result);
			if (index == DRAG_INDEX)
				dlv_main.onRefreshComplete();
			else if (index == LOADMORE_INDEX)
				dlv_main.onLoadMoreComplete(false);
		}

	}
先宣告一點,這個只是個示例,所以這部分程式碼寫的不夠友好,也請見諒.

就說道這裡,最後展示一下效果:

                

至於如果顯示,如何adapter.notifyDataSetChanged();那就要大家開發時候自己調理了.

最後說明一點:網上有好多介紹下拉重新整理的例子,但是他們沒有對滑動進行處理,比如,我下拉的時候現在不想重新整理了,這時我又向上滑動,正常的處理,應該滑動到FirstItemIndex=1就是頂部,滑動就結束了.(意思就是要下拉和listview正常滑動要分開)可是網上一些案例都沒有對之處理,用起來不友好,大家可以看看成功案例,那些新浪,騰訊,百度等.

解決方法:(這是onTouch方法中的一部分.)

/***
		 * 如果是ListView本身的拉動,那麼返回true,這樣ListView不可以拖動.
		 * 如果不是ListView的拉動,那麼呼叫父類方法,這樣就可以上拉執行.
		 */
		if (isScroller) {
			return super.onTouchEvent(ev);
		} else {
			return true;
		}
要問Why的話,那麼你就要去詳細看Touch種種事件,記住,這裡用到的不是分發與攔截,分發攔截流程如下:

Activity 的dispatchTouchEvent開始分發給子的View,如果該View是ViewGroup的話,那麼執行其dispatchTouchEvent進行分發,在執行相應的onInterceptTouchEvent攔截.如果要想實現上訴說的那種效果,那麼在自定義ListView中對攔截分發方法是無效的,只有在ListView的上一層進行處理,比我我們在外層自定義一個佈局,等等,實現起來總之麻煩一個字,其實我們也可以考慮考慮onTouchEvent事件的實現,

ListView.java

 @Override
    public boolean onTouchEvent(MotionEvent ev) {
        if (mItemsCanFocus && ev.getAction() == MotionEvent.ACTION_DOWN && ev.getEdgeFlags() != 0) {
            // Don't handle edge touches immediately -- they may actually belong to one of our
            // descendants.
            return false;
        }
        return super.onTouchEvent(ev);
    }

繼續點選檢視父類,這裡就不顯示了,自己可以檢視原始碼,其實就是我們ListView滑動的具體實現,而此時我們只是想臨時遮蔽掉此滑動,那麼我們只需要不呼叫父類的onTouchEvent不就OK的,是的,確實如此,而何時進行遮蔽,大家就仔細看上面原始碼實現吧,解釋的也很清楚,這樣大家都明白了吧。注:有疑問請留言!之前這個例子

知識拓展:

首先我們還是看一些案例:

      

效果就是可以上下拖拽.而用在最多的地方就是ListView,而普通的佈局拖拽直接自定義佈局就OK了,詳情請參考上面連線那篇文章.

實現起來也不是很麻煩,就是對上面那個自定義類稍作修改,把底部也做成動態拖拽效果就OK了.

這裡不詳細講解,因為註釋相當明確,如有疑問,請指出.

程式碼如下

package com.jj.drag;

import android.content.Context;
import android.os.AsyncTask;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
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.AbsListView.OnScrollListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.ProgressBar;
import android.widget.RelativeLayout;
import android.widget.TextView;

/***
 * 自定義拖拉ListView
 * 
 * @author zhangjia
 * 
 */
public class DragListView extends ListView implements OnScrollListener,
		OnClickListener {
	// 下拉ListView列舉所有狀態
	private enum DListViewState {
		LV_NORMAL, // 普通狀態
		LV_PULL_REFRESH, // 下拉狀態(為超過mHeadViewHeight)

	}

	// 點選載入更多列舉所有狀態
	private enum DListViewLoadingMore {
		LV_NORMAL, // 普通狀態
		LV_PULL_REFRESH, // 上拉狀態(為超過mHeadViewHeight)
	}

	private View mHeadView, mFootView;// 頭部headView

	private int mHeadViewWidth; // headView的寬(mHeadView)
	private int mHeadViewHeight;// headView的高(mHeadView)

	private int mFirstItemIndex = -1;// 當前檢視能看到的第一個項的索引

	private int mLastItemIndex = -1;// 當前檢視中是否是最後一項.

	// 用於保證startY的值在一個完整的touch事件中只被記錄一次
	private boolean mIsRecord = false;// 針對下拉

	private boolean mIsRecord_B = false;// 針對上拉

	private int mStartY, mMoveY;// 按下是的y座標,move時的y座標

	private DListViewState mlistViewState = DListViewState.LV_NORMAL;// 拖拉狀態.(自定義列舉)

	private DListViewLoadingMore loadingMoreState = DListViewLoadingMore.LV_NORMAL;// 載入更多預設狀態.

	private final static int RATIO = 2;// 手勢下拉距離比.

	private boolean isScroller = true;// 是否遮蔽ListView滑動。

	private MyAsynTask myAsynTask;// 任務
	private final static int DRAG_UP = 1, DRAG_DOWN = 2;

	public DragListView(Context context) {
		super(context, null);
		initDragListView(context);
	}

	public DragListView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initDragListView(context);
	}

	/***
	 * 初始化ListView
	 */
	public void initDragListView(Context context) {

		initHeadView(context);// 初始化該head.

		initFooterView(context);// 初始化footer

		setOnScrollListener(this);// ListView滾動監聽
	}

	/***
	 * 初始話頭部HeadView
	 * 
	 * @param context
	 *            上下文
	 * @param time
	 *            上次更新時間
	 */
	public void initHeadView(Context context) {
		mHeadView = LayoutInflater.from(context).inflate(R.layout.head, null);
		measureView(mHeadView);
		// 獲取寬和高
		mHeadViewWidth = mHeadView.getMeasuredWidth();
		mHeadViewHeight = mHeadView.getMeasuredHeight();

		addHeaderView(mHeadView, null, false);// 將初始好的ListView add進拖拽ListView
		// 在這裡我們要將此headView設定到頂部不顯示位置.
		mHeadView.setPadding(0, -1 * mHeadViewHeight, 0, 0);

	}

	/***
	 * 初始化底部載入更多控制元件
	 */
	private void initFooterView(Context context) {
		mFootView = LayoutInflater.from(context).inflate(R.layout.head, null);
		addFooterView(mFootView, null, false);// 將初始好的ListView add進拖拽ListView
		// 在這裡我們要將此FooterView設定到底部不顯示位置.
		mFootView.setPadding(0, -1 * mHeadViewHeight, 0, 0);
	}

	/***
	 * 作用:測量 headView的寬和高.
	 * 
	 * @param child
	 */
	private void measureView(View child) {
		ViewGroup.LayoutParams p = child.getLayoutParams();
		if (p == null) {
			p = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.FILL_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);
	}

	/***
	 * touch 事件監聽
	 */
	@Override
	public boolean onTouchEvent(MotionEvent ev) {

		switch (ev.getAction()) {
		// 按下
		case MotionEvent.ACTION_DOWN:
			doActionDown_B(ev);
			doActionDown(ev);
			break;
		// 移動
		case MotionEvent.ACTION_MOVE:
			doActionMove_B(ev);
			doActionMove(ev);
			break;
		// 擡起
		case MotionEvent.ACTION_UP:
			doActionUp_B(ev);
			doActionUp(ev);
			break;
		default:
			break;
		}

		/***
		 * 如果是ListView本身的拉動,那麼返回true,這樣ListView不可以拖動.
		 * 如果不是ListView的拉動,那麼呼叫父類方法,這樣就可以上拉執行.
		 */
		if (isScroller) {
			return super.onTouchEvent(ev);
		} else {
			return true;
		}

	}

	/***
	 * 摁下操作
	 * 
	 * 作用:獲取摁下是的y座標
	 * 
	 * @param event
	 */
	void doActionDown(MotionEvent event) {
		// 如果是第一項且是一次touch
		if (mIsRecord == false && mFirstItemIndex == 0) {
			mStartY = (int) event.getY();
			mIsRecord = true;
		}
	}

	/***
	 * 摁下操作 底部
	 * 
	 * 作用:獲取摁下是的y座標
	 */
	void doActionDown_B(MotionEvent event) {
		// 如果是第一項且是一次touch
		if (mIsRecord_B == false && mLastItemIndex == getCount()) {
			mStartY = (int) event.getY();
			mIsRecord_B = true;
		}
	}

	/***
	 * 拖拽移動操作
	 * 
	 * @param event
	 */
	void doActionMove(MotionEvent event) {

		// 判斷是否是第一項,若不是直接返回
		mMoveY = (int) event.getY();// 獲取實時滑動y座標

		// 檢測是否是一次touch事件.
		if (mIsRecord == false && mFirstItemIndex == 0) {
			mStartY = (int) event.getY();
			mIsRecord = true;
		}
		// 直接返回說明不是第一項
		if (mIsRecord == false)
			return;

		// 向下啦headview移動距離為y移動的一半.(比較友好)
		int offset = (mMoveY - mStartY) / RATIO;

		switch (mlistViewState) {
		// 普通狀態
		case LV_NORMAL: {
			// 說明下拉
			if (offset > 0) {
				// 設定headView的padding屬性.
				mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);
				mlistViewState = DListViewState.LV_PULL_REFRESH;// 下拉狀態
			}
		}
			break;
		// 下拉狀態
		case LV_PULL_REFRESH: {
			setSelection(0);// 時時保持在頂部.
			// 設定headView的padding屬性.
			mHeadView.setPadding(0, offset - mHeadViewHeight, 0, 0);
			if (offset < 0) {
				/***
				 * 要明白為什麼isScroller = false;
				 */
				isScroller = false;
				mlistViewState = mlistViewState.LV_NORMAL;
			}
		}
			break;
		default:
			return;
		}
	}

	void doActionMove_B(MotionEvent event) {
		mMoveY = (int) event.getY();// 獲取實時滑動y座標
		// 檢測是否是一次touch事件.(若mFirstItemIndex為0則要初始化mStartY)
		if (mIsRecord_B == false && mLastItemIndex == getCount()) {
			mStartY = (int) event.getY();
			mIsRecord_B = true;
		}
		// 直接返回說明不是最後一項
		if (mIsRecord_B == false)
			return;

		// 向下啦headview移動距離為y移動的一半.(比較友好)
		int offset = (mMoveY - mStartY) / RATIO;

		switch (loadingMoreState) {
		// 普通狀態
		case LV_NORMAL: {
			// 說明上拉
			if (offset < 0) {
				int distance = Math.abs(offset);
				// 設定headView的padding屬性.
				mFootView.setPadding(0, distance - mHeadViewHeight, 0, 0);
				loadingMoreState = loadingMoreState.LV_PULL_REFRESH;// 下拉狀態
			}
		}
			break;
		// 上拉狀態
		case LV_PULL_REFRESH: {
			setSelection(getCount() - 1);// 時時保持最底部
			// 設定headView的padding屬性.
			int distance = Math.abs(offset);
			mFootView.setPadding(0, distance - mHeadViewHeight, 0, 0);
			// 說明下滑
			if (offset > 0) {
				/***
				 * 要明白為什麼isScroller = false;
				 */
				isScroller = false;
				loadingMoreState = loadingMoreState.LV_NORMAL;
			}
		}
			break;
		default:
			return;
		}
	}

	/***
	 * 手勢擡起操作
	 * 
	 * @param event
	 */
	public void doActionUp(MotionEvent event) {
		mIsRecord = false;// 此時的touch事件完畢,要關閉。
		mIsRecord_B = false; // 此時的touch事件完畢,要關閉。
		isScroller = true;// ListView可以Scrooler滑動.
		mlistViewState = mlistViewState.LV_NORMAL;// 狀態也迴歸最初狀態

		// 執行相應動畫.
		myAsynTask = new MyAsynTask();
		myAsynTask.execute(DRAG_UP);

	}

	private void doActionUp_B(MotionEvent event) {
		mIsRecord = false;// 此時的touch事件完畢,要關閉。
		isScroller = true;// ListView可以Scrooler滑動.

		loadingMoreState = loadingMoreState.LV_NORMAL;// 狀態也迴歸最初狀態

		// 執行相應動畫.
		myAsynTask = new MyAsynTask();
		myAsynTask.execute(DRAG_DOWN);
	}

	/***
	 * ListView 滑動監聽
	 */
	@Override
	public void onScrollStateChanged(AbsListView view, int scrollState) {

	}

	@Override
	public void onScroll(AbsListView view, int firstVisibleItem,
			int visibleItemCount, int totalItemCount) {
		mFirstItemIndex = firstVisibleItem;
		mLastItemIndex = firstVisibleItem + visibleItemCount;

	}

	@Override
	public void onClick(View v) {

	}

	/***
	 * 用於產生動畫
	 * 
	 * @author zhangjia
	 * 
	 */
	private class MyAsynTask extends AsyncTask<Integer, Integer, Void> {
		private final static int STEP = 30;// 步伐
		private final static int TIME = 5;// 休眠時間
		private int distance;// 距離(該距離指的是:mHeadView的PaddingTop+mHeadView的高度,及預設位置狀態.)
		private int number;// 迴圈執行次數.
		private int disPadding;// 時時padding距離.
		private int DRAG;

		@Override
		protected Void doInBackground(Integer... params) {
			try {
				this.DRAG = params[0];
				if (params[0] == DRAG_UP) {
					// 獲取距離.
					distance = mHeadView.getPaddingTop()
							+ Math.abs(mHeadViewHeight);
				} else {
					// 獲取距離.
					distance = mFootView.getPaddingTop()
							+ Math.abs(mHeadViewHeight);
				}

				// 獲取迴圈次數.
				if (distance % STEP == 0) {
					number = distance / STEP;
				} else {
					number = distance / STEP + 1;
				}
				// 進行迴圈.
				for (int i = 0; i < number; i++) {
					Thread.sleep(TIME);
					publishProgress(STEP);
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return null;
		}

		@Override
		protected void onProgressUpdate(Integer... values) {
			super.onProgressUpdate(values);

			switch (DRAG) {
			case DRAG_UP:
				disPadding = Math.max(mHeadView.getPaddingTop() - STEP, -1
						* mHeadViewHeight);
				mHeadView.setPadding(0, disPadding, 0, 0);// 迴歸.
				break;
			case DRAG_DOWN:
				disPadding = Math.max(mFootView.getPaddingTop() - STEP, -1
						* mHeadViewHeight);
				mFootView.setPadding(0, disPadding, 0, 0);// 迴歸.
				break;
			default:
				break;
			}

		}

	}

}
執行效果:

                             

         預設效果                                             下拉拖拽效果(會自動回縮)               上拉拖拽效果(會自動回縮)           

前面那章實現起來有點小BUG,正在處理,不過這個實現起來沒有發現什麼BUG,要說BUG的話,那麼就是優化,因為我覺得上面效果是實現了,可是效能覺得有點差,比如說“我每次UP的時候要執行任務,那麼就要建立任務物件,你想想看,每次執行都要建立,那麼要建立多少物件,雖說JAVA虛擬機器會自動回收,但是總覺得不是很完善,嗯,臨時就如此了,自己在研究研究看.

至於微信,陌陌等大多數應用都是(資料少的話,就上下都可以拖拽,只是一個人性效果,而資料多的話,上部用於載入過時資料.下部只是個形式.),效果實現起來也不難,只是進行了些判斷,效果嘛,其實上面自定義ListView整理下就OK了.

上面我詳細給出了兩個自定義原始碼的實現,大家可以直接引用.

在這裡我將原始碼上傳,如果上面看的不明白的話,你可以下載,只要你耐心看,我相信大家都能弄明白,都會進行響應擴充套件的.其實我們要的就是創新,而不是簡單應用.

原始碼一

原始碼二


就說到這裡,如有疑問請留言。

另外,如果對您有幫助的話,記得贊一個哦.

在此:Thanks for you !

相關推薦

android ListView部下重新整理下部載入具體實現拓展

這次就不上圖了,例子太多太多了,想必大家都見過.這個功能的實現,簡直是開發者必備的. 我也不過多介紹了,網上詳細介紹的部落格太多太多了,若想深入瞭解,請參考網上其他博文. 在這裡,我只是按照自己的理解,模擬實現了一個,順便程式碼貢獻出來. 我對之詳細標明的註釋,想必如果不懂

jq實現載入分頁功能

頁面上實現類似於下拉載入更多的功能,這種是點選載入更多 。 大致思路是: 首先Ajax獲取到下一頁內容,返回json格式資料,如果是跨域請求可以用jsonp返回,通過jq的append()到某個元素後面 此時分頁的page+1,可以在“載入更多”按鈕上把總頁數和當前

懶人載入載入

//js //點選載入更多 var page = 1; $(".load_more").on('click',function (){ var thisID = $('.layui-this').attr("lay-id");

載入載入5條,然後依次分批載入

html頁面 <center>              <div id="more" data-status="1" onclick="app.hospital.dept.busy.loadMore();" style="margin: 20px;dis

載入外掛

<?PHP    要求( '../類/ connect.php');    要求( '../類/ db_sql.php');    要求( '../資料/ dbcache / class.php');    if($ _ POST [action] =='getmorenews'){    $表=用ht

ajax 載入,出現後面的內容,一次載入十條內容

1.在html部分增加兩句程式碼 <div id="more" data-status="1"> 載入更多 </div> <input type="hidden" id="page" value="2">

vux loadmore + axios 實現載入

在微信專案中有應用過幾個上拉載入更多的元件,但總會出現一些相容性方面的bug,需要各種補漏(注:元件都是基於iscroll實現的, iscroll原本就有些坑)。Vux也有提供Scroller元件實現上拉載入或下拉重新整理,但官方已經不再維護該元件(未實際使用過,不知是否有坑)。所以這次我們採用更為簡單的方式

PHP+Ajax載入列表資料例項

一款簡單實用的PHP+Ajax點選載入更多列表資料例項,實現原理:通過“更多”按鈕向服務端傳送Ajax請求,PHP根據分頁引數查詢

AndroidListView重新整理載入效果實現

  在Android開發中,下拉重新整理和上拉載入更多在很多app中都會有用到,下面就是具體的實現的方法。 首先,我們自定義一個RefreshListView來繼承與ListView,下面是程式碼: package com.example.downrefresh; import

RecyclerView展示固定資料、載入、下重新整理事件、長按事件、刪除條目、重新整理條目、新增條目、條目載入

1、依賴:    implementation 'com.android.support:recyclerview-v7:27.0.2' 2、activity_main、 <?xml version="1.0" encoding="utf-8"?> <Li

Android listview子控制元件的的事件(轉)

1.先看圖,是否是你想要的   2.佈局檔案<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" androi

微信小程式 下重新整理/載入載入怎麼實現

實現原理: 1、下拉重新整理:由於小程式資料是實時渲染的。我們把data{}內的資料清空重新載入即可實現下拉重新整理。 2、上拉載入更多(頁面上拉觸底事件):新獲取的資料追加到data{}內的原

Android listview的item中button的事件 item和button 可同時點

此種應用情形在android開發中會遇到很遇到很多 在此闡述一下我的解決方案 第一步 給一個listview xml 可根據自己需要自己佈局 第二步 給listview 一個adapter 我們需要在 adapter 中 定義一個點選響應介面 OnCl

Android Demo之旅 ListView底部新增載入按鈕實現資料分頁

在我們的實際專案中,資料應該說是很多的,我們的ListView不可能一下子把資料全部載入進來,我們可以當滾動條滾動到ListView的底部的時候,給一個更多的提示,當我們點選它即載入下一頁的資料,相當與我們的分頁效果,參考網上的東西,寫了一個小小的demo,並總結了一些知識

支付寶賬單分組、重新整理載入效果實現

          專案中賬單需要做二期優化,支付軟體嘛當然向支付寶看齊了。。分析了下支付的實現,github了一圈最後回到程式碼中,merge了兩個哥們的開源專案【開源就是好】。。 我主要分析一下整個merge的程式碼:    1:定義我們下拉重新整理和上拉載入的類【兩個

行文字省略,js顯示

html+css: <div id="textInfos" style=" overflow : hidden; text-overflow: ellipsis; display: -webkit-box; -webkit-line-clamp: 2;

簡易資料分析 08 | Web Scraper 翻頁——按鈕」翻頁

這是簡易資料分析系列的第 8 篇文章。 我們在Web Scraper 翻頁——控制連結批量抓取資料一文中,介紹了控制網頁連結批量抓取資料的辦法。 但是你在預覽一些網站時,會發現隨著網頁的下拉,你需要點選類似於「載入更多」的按鈕去獲取資料,而網頁連結一直沒有變化。 所以控制連結批量抓去資料的方案失效了,所以

Android ListView重新整理載入,帶動畫 自定義控制元件

之前每次 專案中用到ListView 的 下拉重新整理 以及上拉分頁載入 都是 用的 網上 下載 的 類庫, 使用起來 諸多不便 ,於是 趁著有空 ,自己封裝了ListView 讓其 實現 下拉重新整理,以及分頁載入功能。 以下是 效果圖: 當 滑動到 ListView 頂

Android分組列表懸停顯示,分組listView懸停效果,帶下重新整理載入

分組列表,帶下拉重新整理和上拉載入更多【專案地址在文章最後!!】 效果圖: 實現過程,借鑑PinnedHeadListView,但是該demo沒有下拉重新整理功能,故將該控制元件整合到PullToRefresh 庫中,【PullToRefresh 庫為第

Android scrollview中巢狀listview實現listview的下重新整理載入

我們都知道在Android中scrollview和listview都能滑動,如果scrollview巢狀listview會出現一些問題,比如listview不能正常顯示item...但是在一些專案中,一些頁面內容比較多,需要在外面放一個scrollview,裡面還會巢狀li