`
mmdev
  • 浏览: 12885275 次
  • 性别: Icon_minigender_1
  • 来自: 大连
文章分类
社区版块
存档分类
最新评论

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的,是的,确实如此,而何时进行屏蔽,大家就仔细看上面源码实现吧,解释的也很清楚,这样大家都明白了吧。注:有疑问请留言!之前这个例子android 自定义ScrollView实现反弹效果(以及解决和ListView之间的冲突)没有解决这个问题,因为处境不同.(不过正在完善,相信也会完美的实现这些效果,因为原理上是行的通的。)


知识拓展:

首先我们还是看一些案例:


效果就是可以上下拖拽.而用在最多的地方就是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 !











分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics