摘要:本文将带你了解Android应用开发之Android 滑动冲突处理,希望本文对大家学Android有所帮助
本文将带你了解Android应用开发之Android 滑动冲突处理,希望本文对大家学Android有所帮助
常见的有两种
一个控件横向滑动,另一个控件竖向滑动。比如:类似ViewPager,每个页面里面是ListView,不过,我们不用去处理滑动处理,ViewPager内部已经处理好了。
一个控件竖向滑动,另一个控件也竖向滑动。比如:ScollView里面包裹着ListView,这也是需要着重理解掌握的。
Android内置了Scoller,用于实现渐进式的滑动。
创建Scroller对象:Scroller mScroller = new Scroller(context);
重写computeScroll()方法;
最后,在我们的smoothScrollTo方法中调用startScroll方法;
Java代码 Scroller mScroller = new Scroller(context); @Override public void computeScroll() { if (mScroller.computeScrollOffset()){ scrollTo(mScroller.getCurrX(),mScroller.getCurrY()); postInvalidate(); } } //这里smoothScrollTo实现的是x方向的平滑 public void smoothScrollTo(int destX,int destY){ int scrollX=getScrollX(); int deltaX=destX-scrollX; mScroller.startScroll(scrollX,0,deltaX,0,1000);//startScroll函数的形参分别表示:起始位置的x坐标、起始位置的y坐标、x方向要移动的距离、y方向上要移动的距离以及整个滑动过程完成所需的时间。 invalidate(); } Scroller mScroller = new Scroller(context);
@Override
public void computeScroll() {
if (mScroller.computeScrollOffset()){
scrollTo(mScroller.getCurrX(),mScroller.getCurrY());
postInvalidate();
}
}
//这里smoothScrollTo实现的是x方向的平滑
public void smoothScrollTo(int destX,int destY){
int scrollX=getScrollX();
int deltaX=destX-scrollX;
mScroller.startScroll(scrollX,0,deltaX,0,1000);//startScroll函数的形参分别表示:起始位置的x坐标、起始位置的y坐标、x方向要移动的距离、y方向上要移动的距离以及整个滑动过程完成所需的时间。
invalidate();
}
Scroller.computeScrollOffset():这个方法返回true,表示滑动还未结束,还要继续滑动,false表示滑动已结束。
scrollTo:注意,这里其实用的本质是scrollTo(...),所以要求View是有内容的View,平滑移动的也是其内容,而非View本身位置的改变。
postInvalidate():用在非UI线程,其内部最后也是调用invalidate()(同理,invalidate用在UI线程)。
startScroll:当我们构造一个Scroller对象并且调用它的startScroll方法时,Scroller内部其实什么都没做,它只是保存了我们传递的几个参数。
原理:
Scroller到底时如何让View渐进滑动的呢?答案就是startScroll方法下面的invalidate方法。invalidate方法会导致View重绘,在View的draw方法中又会去调用computeScroll方法,computeScroll方法在View中是一个空实现,因此需要我们自己去实现。我们代码中已经实现了computeScroll方法,所以正是如此,它才能动起来。
总结:
Scroller本身不能实现View的滑动,我们需要借助View的computeScroll方法,并让它不断的刷新重绘。
滑动冲突解决方式:
外部拦截法
通过onInterceptTouchEvent(MotionEvent event)
Java代码 @Override public boolean onInterceptTouchEvent(MotionEvent event) { boolean intercepted = false; int x = (int) event.getX(); int y = (int) event.getY(); switch (event.getAction()) { case MotionEvent.ACTION_DOWN: intercepted = false; break; case MotionEvent.ACTION_MOVE: if (父容器需要当前点击事件) { intercepted = true; } else { intercepted = false; } break; case MotionEvent.ACTION_UP: intercepted = false; break; default: break; } mLastXIntercept = x; mLastYIntercept = y; return intercepted; } /* 注意: 1. ACTION_DOWN 一定返回false,不要拦截它,否则根据View事件分发机制,后续ACTION_MOVE与ACTION_UP事件都将默认交给父View去处理! 2. 原则上ACTION_UP 也要返回false,如果返回true,并且滑动事件交给子View处理,那么子View将接收不到ACTION_UP事件,子View的onClick事件也就无法触发。 而父View不一样,如果父View在ACTION_MOVE中开始拦截事件,那么后续ACTION_UP也将默认交给父View处理。 */ @Override
public boolean onInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN:
intercepted = false;
break;
case MotionEvent.ACTION_MOVE:
if (父容器需要当前点击事件) {
intercepted = true;
} else {
intercepted = false;
}
break;
case MotionEvent.ACTION_UP:
intercepted = false;
break;
default:
break;
}
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
/*
注意:
1. ACTION_DOWN 一定返回false,不要拦截它,否则根据View事件分发机制,后续ACTION_MOVE与ACTION_UP事件都将默认交给父View去处理!
2. 原则上ACTION_UP 也要返回false,如果返回true,并且滑动事件交给子View处理,那么子View将接收不到ACTION_UP事件,子View的onClick事件也就无法触发。
而父View不一样,如果父View在ACTION_MOVE中开始拦截事件,那么后续ACTION_UP也将默认交给父View处理。
*/
内部拦截法
通过dispatchTouchEvent(MotionEvent event)
内部拦截法就是指父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器进行处理,这种方法和Android的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作,使用起来较外部拦截法较复杂。
Java代码 @Override public boolean dispatchTouchEvent(MotionEvent ev) { int x= (int) ev.getX(); int y= (int) ev.getY(); switch (ev.getAction()){ case MotionEvent.ACTION_DOWN: parent.requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: int deltaX=x-mLastX; int deltaY=y-mLastY; if (父容器需要此类点击事件){ parent.requestDisallowInterceptTouchEvent(false); } break; case MotionEvent.ACTION_UP: break; default: break; } mLastX=x; mLastY=y; return super.dispatchTouchEvent(ev); } /* 内部拦截法要求父View不能拦截ACTION_DOWN事件,由于ACTION_DOWN不受FLAG_DISALLOW_INTERCEPT标志位控制,一旦父容器拦截ACTION_DOWN那么所有的事件都不会传递给子View。 */ @Override
public boolean dispatchTouchEvent(MotionEvent ev) {
int x= (int) ev.getX();
int y= (int) ev.getY();
switch (ev.getAction()){
case MotionEvent.ACTION_DOWN:
parent.requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
int deltaX=x-mLastX;
int deltaY=y-mLastY;
if (父容器需要此类点击事件){
parent.requestDisallowInterceptTouchEvent(false);
}
break;
case MotionEvent.ACTION_UP:
break;
default:
break;
}
mLastX=x;
mLastY=y;
return super.dispatchTouchEvent(ev);
}
/*
内部拦截法要求父View不能拦截ACTION_DOWN事件,由于ACTION_DOWN不受FLAG_DISALLOW_INTERCEPT标志位控制,一旦父容器拦截ACTION_DOWN那么所有的事件都不会传递给子View。
*/
ViewPager源码分析
借阅理解文章(//blog.csdn.net/huachao1001/article/details/51654692)
ViewPager的滑动冲突处理:
我们知道,ViewGroup是在onInterceptTouchEvent函数中决定是否拦截触摸事件,那么我们就去学习一下ViewPager的onInterceptTouchEvent函数。
Java代码 @Override public boolean onInterceptTouchEvent(MotionEvent ev) { //1. 触摸动作 final int action = ev.getAction() & MotionEventCompat.ACTION_MASK; //2. 时刻要注意触摸是否已经结束 if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) { //3. Release the drag. if (DEBUG) Log.v(TAG, "Intercept done!"); //4. 重置一些跟判断是否拦截触摸相关变量 resetTouch(); //5. 触摸结束,无需拦截 return false; } //6. 如果当前不是按下事件,我们就判断一下,是否是在拖拽切换页面 if (action != MotionEvent.ACTION_DOWN) { //7. 如果当前是正在拽切换页面,直接拦截掉事件,后面无需再做拦截判断 if (mIsBeingDragged) { if (DEBUG) Log.v(TAG, "Intercept returning true!"); return true; } //8. 如果标记为不允许拖拽切换页面,我们就"放过"一切触摸事件 if (mIsUnableToDrag) { if (DEBUG) Log.v(TAG, "Intercept returning false!"); return false; } } //9. 根据不同的动作进行处理 switch (action) { //10. 如果是手指移动操作 case MotionEvent.ACTION_MOVE: { //11. 代码能执行到这里,就说明mIsBeingDragged==false,否则的话,在第7个注释处就已经执行结束了 //12.使用触摸点Id,主要是为了处理多点触摸 final int activePointerId = mActivePointerId; if (activePointerId == INVALID_POINTER) { //13.如果当前的触摸点id不是一个有效的Id,无需再做处理 break; } //14.根据触摸点的id来区分不同的手指,我们只需关注一个手指就好 final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId); //15.根据这个手指的序号,来获取这个手指对应的x坐标 final float x = MotionEventCompat.getX(ev, pointerIndex); //16.在x轴方向上移动的距离 final float dx = x - mLastMotionX; //17.x轴方向的移动距离绝对值 final float xDiff = Math.abs(dx); //18.同理,参照16、17条注释 final float y = MotionEventCompat.getY(ev, pointerIndex); final float yDiff = Math.abs(y - mInitialMotionY); if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff); //19.判断当前显示的页面是否可以滑动,如果可以滑动,则将该事件丢给当前显示的页面处理 //isGutterDrag是判断是否在两个页面之间的缝隙内移动 //canScroll是判断页面是否可以滑动 if (dx != 0 && !isGutterDrag(mLastMotionX, dx) && canScroll(this, false, (int) dx, (int) x, (int) y)) { mLastMotionX = x; mLastMotionY = y; //20.标记ViewPager不去拦截事件 mIsUnableToDrag = true; return false; } //21.如果x移动距离大于最小距离,并且斜率小于0.5,表示在水平方向上的拖动 if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) { if (DEBUG) Log.v(TAG, "Starting drag!"); //22.水平方向的移动,需要ViewPager去拦截 mIsBeingDragged = true; //23.如果ViewPager还有父View,则还要向父View申请将触摸事件传递给ViewPager requestParentDisallowInterceptTouchEvent(true); //24.设置滚动状态 setScrollState(SCROLL_STATE_DRAGGING); //25.保存当前位置 mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop : mInitialMotionX - mTouchSlop; mLastMotionY = y; //26.启用缓存 setScrollingCacheEnabled(true); } else if (yDiff > mTouchSlop) {//27.否则的话,表示是竖直方向上的移动 if (DEBUG) Log.v(TAG, "Starting unable to drag!"); //28.竖直方向上的移动则不去拦截触摸事件 mIsUnableToDrag = true; } if (mIsBeingDragged) { // 29.跟随手指一起滑动 if (performDrag(x)) { ViewCompat.postInvalidateOnAnimation(this); } } break; } //30.如果手指是按下操作 case MotionEvent.ACTION_DOWN: { //31.记录按下的点位置 mLastMotionX = mInitialMotionX = ev.getX(); mLastMotionY = mInitialMotionY = ev.getY(); //32.第一个ACTION_DOWN事件对应的手指序号为0 mActivePointerId = MotionEventCompat.getPointerId(ev, 0); //33.重置允许拖拽切换页面 mIsUnableToDrag = false; //34.标记开始滚动 mIsScrollStarted = true; //35.手动调用计算滑动的偏移量 mScroller.computeScrollOffset(); //36.如果当前滚动状态为正在将页面放置到最终位置, //且当前位置距离最终位置足够远 if (mScrollState == SCROLL_STATE_SETTLING && Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) { //37. 如果此时用户手指按下,则立马暂停滑动 mScroller.abortAnimation(); mPopulatePending = false; populate(); mIsBeingDragged = true; //38.如果ViewPager还有父View,则还要向父View申请将触摸事件传递给ViewPager requestParentDisallowInterceptTouchEvent(true); //39.设置当前状态为正在拖拽 setScrollState(SCROLL_STATE_DRAGGING); } else { //40.结束滚动 completeScroll(false); mIsBeingDragged = false; } if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY + " mIsBeingDragged=" + mIsBeingDragged + "mIsUnableToDrag=" + mIsUnableToDrag); break; } case MotionEventCompat.ACTION_POINTER_UP: onSecondaryPointerUp(ev); break; } //41.添加速度追踪 if (mVelocityTracker == null) { mVelocityTracker = VelocityTracker.obtain(); } mVelocityTracker.addMovement(ev); //42.只有在当前是拖拽切换页面时我们才会去拦截事件 return mIsBeingDragged; } @Override
public boolean onInterceptTouchEvent(MotionEvent ev) {
//1. 触摸动作
final int action = ev.getAction() & MotionEventCompat.ACTION_MASK;
//2. 时刻要注意触摸是否已经结束
if (action == MotionEvent.ACTION_CANCEL || action == MotionEvent.ACTION_UP) {
//3. Release the drag.
if (DEBUG) Log.v(TAG, "Intercept done!");
//4. 重置一些跟判断是否拦截触摸相关变量
resetTouch();
//5. 触摸结束,无需拦截
return false;
}
//6. 如果当前不是按下事件,我们就判断一下,是否是在拖拽切换页面
if (action != MotionEvent.ACTION_DOWN) {
//7. 如果当前是正在拽切换页面,直接拦截掉事件,后面无需再做拦截判断
if (mIsBeingDragged) {
if (DEBUG) Log.v(TAG, "Intercept returning true!");
return true;
}
//8. 如果标记为不允许拖拽切换页面,我们就"放过"一切触摸事件
if (mIsUnableToDrag) {
if (DEBUG) Log.v(TAG, "Intercept returning false!");
return false;
}
}
//9. 根据不同的动作进行处理
switch (action) {
//10. 如果是手指移动操作
case MotionEvent.ACTION_MOVE: {
//11. 代码能执行到这里,就说明mIsBeingDragged==false,否则的话,在第7个注释处就已经执行结束了
//12.使用触摸点Id,主要是为了处理多点触摸
final int activePointerId = mActivePointerId;
if (activePointerId == INVALID_POINTER) {
//13.如果当前的触摸点id不是一个有效的Id,无需再做处理
break;
}
//14.根据触摸点的id来区分不同的手指,我们只需关注一个手指就好
final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId);
//15.根据这个手指的序号,来获取这个手指对应的x坐标
final float x = MotionEventCompat.getX(ev, pointerIndex);
//16.在x轴方向上移动的距离
final float dx = x - mLastMotionX;
//17.x轴方向的移动距离绝对值
final float xDiff = Math.abs(dx);
//18.同理,参照16、17条注释
final float y = MotionEventCompat.getY(ev, pointerIndex);
final float yDiff = Math.abs(y - mInitialMotionY);
if (DEBUG) Log.v(TAG, "Moved x to " + x + "," + y + " diff=" + xDiff + "," + yDiff);
//19.判断当前显示的页面是否可以滑动,如果可以滑动,则将该事件丢给当前显示的页面处理
//isGutterDrag是判断是否在两个页面之间的缝隙内移动
//canScroll是判断页面是否可以滑动
if (dx != 0 && !isGutterDrag(mLastMotionX, dx) &&
canScroll(this, false, (int) dx, (int) x, (int) y)) {
mLastMotionX = x;
mLastMotionY = y;
//20.标记ViewPager不去拦截事件
mIsUnableToDrag = true;
return false;
}
//21.如果x移动距离大于最小距离,并且斜率小于0.5,表示在水平方向上的拖动
if (xDiff > mTouchSlop && xDiff * 0.5f > yDiff) {
if (DEBUG) Log.v(TAG, "Starting drag!");
//22.水平方向的移动,需要ViewPager去拦截
mIsBeingDragged = true;
//23.如果ViewPager还有父View,则还要向父View申请将触摸事件传递给ViewPager
requestParentDisallowInterceptTouchEvent(true);
//24.设置滚动状态
setScrollState(SCROLL_STATE_DRAGGING);
//25.保存当前位置
mLastMotionX = dx > 0 ? mInitialMotionX + mTouchSlop :
mInitialMotionX - mTouchSlop;
mLastMotionY = y;
//26.启用缓存
setScrollingCacheEnabled(true);
} else if (yDiff > mTouchSlop) {//27.否则的话,表示是竖直方向上的移动
if (DEBUG) Log.v(TAG, "Starting unable to drag!");
//28.竖直方向上的移动则不去拦截触摸事件
mIsUnableToDrag = true;
}
if (mIsBeingDragged) {
// 29.跟随手指一起滑动
if (performDrag(x)) {
ViewCompat.postInvalidateOnAnimation(this);
}
}
break;
}
//30.如果手指是按下操作
case MotionEvent.ACTION_DOWN: {
//31.记录按下的点位置
mLastMotionX = mInitialMotionX = ev.getX();
mLastMotionY = mInitialMotionY = ev.getY();
//32.第一个ACTION_DOWN事件对应的手指序号为0
mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
//33.重置允许拖拽切换页面
mIsUnableToDrag = false;
//34.标记开始滚动
mIsScrollStarted = true;
//35.手动调用计算滑动的偏移量
mScroller.computeScrollOffset();
//36.如果当前滚动状态为正在将页面放置到最终位置,
//且当前位置距离最终位置足够远
if (mScrollState == SCROLL_STATE_SETTLING &&
Math.abs(mScroller.getFinalX() - mScroller.getCurrX()) > mCloseEnough) {
//37. 如果此时用户手指按下,则立马暂停滑动
mScroller.abortAnimation();
mPopulatePending = false;
populate();
mIsBeingDragged = true;
//38.如果ViewPager还有父View,则还要向父View申请将触摸事件传递给ViewPager
requestParentDisallowInterceptTouchEvent(true);
//39.设置当前状态为正在拖拽
setScrollState(SCROLL_STATE_DRAGGING);
} else {
//40.结束滚动
completeScroll(false);
mIsBeingDragged = false;
}
if (DEBUG) Log.v(TAG, "Down at " + mLastMotionX + "," + mLastMotionY
+ " mIsBeingDragged=" + mIsBeingDragged
+ "mIsUnableToDrag=" + mIsUnableToDrag);
break;
}
case MotionEventCompat.ACTION_POINTER_UP:
onSecondaryPointerUp(ev);
break;
}
//41.添加速度追踪
if (mVelocityTracker == null) {
mVelocityTracker = VelocityTracker.obtain();
}
mVelocityTracker.addMovement(ev);
//42.只有在当前是拖拽切换页面时我们才会去拦截事件
return mIsBeingDragged;
}
我们看看ViewPager是如何决定是拦截还是不拦截,从源码上面看出,但斜率小于0.5时,则要拦截,否则不拦截,斜率是什么情况呢?高中数学可知,在第一象限中,越靠近y轴的直线,斜率越大,越靠近x轴直线斜率越小,先看简单图示:
(何为斜率:斜率就是倾斜程度,斜率一般用k表示,斜率k值为直线与x轴正方向夹角的正切值,若直线上任意两点为(x1,y1)、(x2,y2)则直线斜率k=(y2-y1)/(x2-x1).直线平行于y轴,斜率不存在,平行于x轴,斜率为0)
本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发之Android频道!
您输入的评论内容中包含违禁敏感词
我知道了
请输入正确的手机号码
请输入正确的验证码
您今天的短信下发次数太多了,明天再试试吧!
我们会在第一时间安排职业规划师联系您!
您也可以联系我们的职业规划师咨询:
版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
沪公网安备 31011502005948号