针对自定义组件上拉刷新下拉加载更多PullToRefreshView的分析(二)

第二篇要从触摸事件讲起,前面已经可以看到各个子view已经加载进来,并且做了一些初始化,于是接下来,就是对用户输入做一些判断操作,让view可以跟着用户手势进行相应的改变。

转载请注明出处:http://blog.csdn.net/baozhuifeng/article/details/9854051

当你看见手指拖着列表往下拉的时候,头部的view会慢慢往下滑而不是一下子就跳出来,这个也是根据子view距离父view的距离来做相应的判断然后让view一点点的出来的,可以说这里面的操作大部分都跟距离位置相关,这也是值得学习的地方,因为细节的一些不同才能更加做出体验自然的应用。

这里需要对触摸手势做出反应,需要重写两个方法,分别是onInterceptTouchEvent(MotionEvent e)和onTouchEvent(MotionEvent event),onInterceptTouchEvent()用于处理事件并改变事件的传递方向,系统会根据它的返回值决定是把接下来的触摸事件让给自己的onTouchEvent()还是子view的onTouchEvent()来处理,而onTouchEvent() 用于处理事件,返回值决定当前控件是否消费(consume)了这个事件。对这一块有疑问的,可以参考这个链接所进行的解释:http://www.cnblogs.com/kingcent/archive/2011/03/08/1977064.html

搞懂了上面的内容,接下来的事情就好办了,我们只需要对MotionEvent是上下还是移动还是取消进行相应的判断并进行一些view的改动就能达到我们的目的。

首先我们来看下onInterceptTouchEvent()这个方法:

	@Override
	public boolean onInterceptTouchEvent(MotionEvent e) {
		int y = (int) e.getRawY();
		switch (e.getAction()) {
		case MotionEvent.ACTION_DOWN:
			// 首先拦截down事件,记录y坐标
			mLastMotionY = y;
			break;
			
		case MotionEvent.ACTION_MOVE:
			// deltaY > 0 是向下运动,< 0是向上运动
			int deltaY = y - mLastMotionY;
			if (isRefreshViewScroll(deltaY)) {
				return true;
			}
			break;
		case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_CANCEL:
			break;
		}
		return false;
	}
可以看到,第一句用int y = (int) e.getRawY();得到了触摸事件触发时第一个点的Y坐标值,并且用e.getAction()得到了MotionEvent事件的值,很显然,第一个事件必然就是MotionEvent.ACTION_DOWN,记录了起始Y坐标之后,返回false,到第二次事件为MotionEvent.ACTION_MOVE的时候,调用了isRefreshViewScroll()来判断列表是滑到了顶部还是底部,这样就可以根据它的返回值来确定接下来是不是该让父view也就是PullToRefreshView中的两个refreshView进行滑动了。如果是isRefreshViewScroll()返回真,则onInterceptTouchEvent() 也返回真,那就该由父view的onTouchEvent() 处理接下来触摸手势事件。

看下isRefreshViewScroll()的部分关键代码

//返回屏幕上可见的第一个view在adapter中的位置&&返回adapterview中第一个view的上边界与父view之间的像素距离
				if (mAdapterView.getFirstVisiblePosition() == 0
						&& child.getTop() == 0) {
					mPullState = PULL_DOWN_STATE;//设置下拉状态为正在下拉
					return true;//返回true,这个时候父view的onTouchEvent开始接受触摸事件
				}
注释里面已经讲得比较清楚了,接下来它如果返回true的话就到了父view的onTouchEvent() 来处理下拉或者上拉事件了。

来看下onTouchEvent() 中的这部分代码

		case MotionEvent.ACTION_MOVE:
			int deltaY = y - mLastMotionY;
			if (mPullState == PULL_DOWN_STATE) {
				// PullToRefreshView执行下拉
				Log.i(TAG, " pull down!parent view move!");
				headerPrepareToRefresh(deltaY);
				// setHeaderPadding(-mHeaderViewHeight);
			} else if (mPullState == PULL_UP_STATE) {
				// PullToRefreshView执行上拉
				Log.i(TAG, "pull up!parent view move!");
				footerPrepareToRefresh(deltaY);
			}
			mLastMotionY = y;
			break;
这里的mPullState是之前在isRefreshViewScroll()中已经被赋值了,这里进行判断是上拉还是下拉,从而进行相应的刷新操作。

来看下headerPrepareToRefresh()做了些什么,可以看到其实是对header 的refreshView进行一些变动,改隐藏的隐藏该显示的显示,比如从“下拉刷新”的文字变成“松开手刷新”之类的,以及对那个箭头的动画启动。

	private void headerPrepareToRefresh(int deltaY) {
		int newTopMargin = changingHeaderViewTopMargin(deltaY);
		// 当header view的topMargin>=0时,说明已经完全显示出来了,修改header view 的提示状态
		if (newTopMargin >= 0 && mHeaderState != RELEASE_TO_REFRESH) {
			mHeaderTextView.setText(R.string.pull_to_refresh_release_label);
			mHeaderUpdateTextView.setVisibility(View.VISIBLE);
			mHeaderImageView.clearAnimation();
			mHeaderImageView.startAnimation(mFlipAnimation);
			mHeaderState = RELEASE_TO_REFRESH;
		} else if (newTopMargin < 0 && newTopMargin > -mHeaderViewHeight) {// 拖动时没有释放
			mHeaderImageView.clearAnimation();
			mHeaderImageView.startAnimation(mFlipAnimation);
			// mHeaderImageView.
			mHeaderTextView.setText(R.string.pull_to_refresh_pull_label);
			mHeaderState = PULL_TO_REFRESH;
		}
	}
上面代买中changingHeaderViewTopMargin(int deltaY) 起到了下拉的弹簧效果,实际上市根据你下拉的距离来乘以小于1的正数来让你感觉拉着有感觉,这里面还做了限制上拉下拉同时触发的情况,这里可以自己注释掉那个判断去实验一下。

当我们的newTopMargin大于等于0也就是表示那个refreshView已经完全显示出来了,就可以放开手进行刷新操作了,于是下面的事件就是判断是上拉刷新还是下拉刷新,或者是refreshView还没有完全显示出来就进行了释放操作是不会进行刷新的,而是隐藏refreshView,然后用invalidate();告知UI线程进行界面刷新。

case MotionEvent.ACTION_UP:
		case MotionEvent.ACTION_CANCEL:
			int topMargin = getHeaderTopMargin();
			if (mPullState == PULL_DOWN_STATE) {
				if (topMargin >= 0) {
					// 开始刷新
					headerRefreshing();
				} else {
					// 还没有执行刷新,重新隐藏
					setHeaderTopMargin(-mHeaderViewHeight);
				}
			} else if (mPullState == PULL_UP_STATE) {
				if (Math.abs(topMargin) >= mHeaderViewHeight
						+ mFooterViewHeight) {
					// 开始执行footer 刷新
					footerRefreshing();
				} else {
					// 还没有执行刷新,重新隐藏
					setHeaderTopMargin(-mHeaderViewHeight);
				}
			}
			break;
我们来看下刷新操作

	private void headerRefreshing() {
		mHeaderState = REFRESHING;
		setHeaderTopMargin(0);
		mHeaderImageView.setVisibility(View.GONE);
		mHeaderImageView.clearAnimation();
		mHeaderImageView.setImageDrawable(null);
		mHeaderProgressBar.setVisibility(View.VISIBLE);
		mHeaderTextView.setText(R.string.pull_to_refresh_refreshing_label);
		if (mOnHeaderRefreshListener != null) {
			mOnHeaderRefreshListener.onHeaderRefresh(this);
		}
	}

这里就直接设置了这个refreshView的各种组件的状态,并用mOnHeaderRefreshListener.onHeaderRefresh(this);(在TestListView中进行了重写的回调方法),去调用onHeaderRefreshComplete()进行相应的刷新完成之后的恢复操作。

至此,整个PullToRefreshView的自定义组件的概念就出来了,相信以后遇到此种类似组件的时候,也能够达到举一反三的效果。








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值