第三章 View的事件体系
ide
3.1 View基础知识
布局
3.1.1 什么是viewpost
View 是Android中全部控件的基类,是一种界面层的控件的一种抽象,它表明了一个控件。动画
3.1.2 View的位置参数spa
View的位置主要由它的四个顶点来决定,分别对应于View的四个属性:top,left,right,bottom;须要注意的是这些坐标都是相对于View的父容器来讲的;(在Android中X轴和Y轴的正方向分别为右和下)。code
3.0开始新增的属性blog
x- view左上角横坐标;事件
y- view左上角纵坐标;ci
translationX- view左上角相对于父容器的水平偏移量;get
translationY- view左上角相对于父容器的垂直偏移量;
View在平移过程当中,top和left表示的是原始左上角的位置信息,其值并不会发生变化,此时发生变化的是x,y,translationX,translationY这四个参数。
3.1.3 MotionEvent和TouchSlop
1 MotionEvent
getX和getY方法返回的是当前View左上角的x和y坐标,而getRawC和getRawY方法返回的是相对于手机屏幕左上角的x和y坐标。
2 TouchSlop
TouchSlop是系统所能识别出的被认为是滑动的最小距离,是一个常量,经过以下方式能够得到这个常量:ViewConfiguration.get(getContext()).getScaledTouchSlop();
3.1.4 VelocityTracker,GestureDetector和Scroller
1 VelocityTracker
VelocityTracker—速度追踪,追踪手指在滑动过程当中的速度(水平垂直两个方向),注意速度能够为负值,当手指从右向左滑动时,水平方向速度即为负值。
2 GestureDetector
GestureDetector—手势检测,用于辅助检测用户的单击,滑动,长,双击等行为。
建议:若是只是监听滑动相关的,在onTouchEvent中实现,若是要监听双击行为就用GestureDetector。
3 Scroller
Scroller—弹性滑动,用于实现View的弹性滑动(有过渡效果的滑动),须要配合View的computeScroll方法使用。
3.2 View的滑动
实现View滑动的三种方式:
1 经过View自己的scrollTo/scrollBy方法实现滑动;
2 经过动画给View施加平移效果实现滑动;
3 经过改变View的LayoutParams使得View从新布局实现滑动;
3.2.1 使用scrollTo/scrollBy
scrollTo/scrollBy的源码以下:
public void scrollTo(int x ,int y){ if(mScrollX!=x||mScrollY!=y){ int oldX=mScrollX; int oldY=mScrollY; mScrollX=x; mScrollY=y; invalidateParentCaches(); onScrollChanged(mScrollX,mScrollY,oldX,oldY); if(!awakenScrollBars()){ postInvalidateOnAnimation(); } } } public void scrollBy(int x,int y){ scrollTo(mScrollX+x,mScrollY+y); }
其中mScrollX的值老是等于View左边缘和View内容左边缘在水平方向的距离,mScrollY的值老是等于View的上边缘和View内容上边缘在垂直方向上的距离;
scrollTo/scrollBy只能改变View内容的位置而不能改变自身在布局中的位置。
也就是说使用scrollTo/scrollBy来实现View的滑动只能将View的内容进行移动,并不能将View自己进行移动。
3.2.2 使用动画
使用动画来移动View 主要是操做View的translationX和translationY属性。(传统的View动画和属性动画)
View动画是对View的影像操做,它并不能真正改变View的位置参数,包括宽/高。
3.2.3 改变布局参数
主要是经过改变View的LayoutParams来实现;下面的代码展现如何给一个mButton1从新设置LayoutParams:
MarginLayoutParams params=(MarginLayoutParams)mButton1.getLayoutParams();
params.width+=100; params.leftMargin+=100; mButton1.requestLayout();
//或者mButton1.setLayoutParams(params)
3.2.4各类滑动方式的对比
scrollTo/scrollBy:操做简单,适合对View内容的滑动;
动画:操做简单,主要适用于没有交互的View和实现复杂的动画效果;
改变布局参数:操做稍微复杂,适用于有交互的View。
3.3弹性滑动
3.3.1使用Scroller
注意Scroller产生的滑动也是对View内容的滑动而非View自己位置的改变。
Scroller的典型使用方式以下:
Scroller scroller=new Scroller(context); //缓慢滚动到指定位置 private void smoothScrollTo(int destX,int destY){ int scrollx=getScrollX(); int deltaX=destX-scrollX; //1000ms内滑向destX,效果就是慢慢滑动 scroller.startScroll(scrollX,0,deltaX,0,1000); invalidate(); } @Override public void computeScroll(){ if(scroller.computeScrollOffset()){ scrollTo(scroller.getCurrX(),scroller.getCurrY()); postInvalidate(); } }
Scroller的工做机制:Scroller自己并不能实现View的滑动,它须要配合View的computeScroll方法才能完成弹性滑动的效果,它不断的让View重绘,而每一次重绘距滑动起始时间会有一个时间间隔,经过这个时间间隔Scroller就能够得出View当前的滑动位置,知道了滑动位置就能够经过scrollTo方法来View的滑动。就这样,View的每一次重绘都会致使View进行小幅度的滑动,而屡次的小幅度滑动就组成了弹性滑动。
3.4 View的时间分发机制
3.4.1 点击事件的传递规则
点击事件的分发过程主要由三个方法来完成。
dispatchTouchEvent() :用来进行事件的分发,若是事件可以传递给当前View,此方法必定会被调用,返回结果受当前View的onTouchEvent和下级View的dispatchTouchEvent方法影响,表示是否消耗当前事件。
onInterceptTouchEvent() :在dispatchTouchEvent方法内部调用,判断是否拦截某个事件,若是当前View拦截了某个事件,那么在同一个事件序列中,此方法不会在被调用,返回结果表示是否拦截当前事件。
onTouchEvent() :在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,若是不消耗,则在同一个事件序列中,当前View没法再次接收到其余事件。
事件序列是指从手指接触屏幕的那一刻起,到手指离开屏幕的那一刻结束,在这个过程当中所产生的一系列事件,这个事件序列以down事件开始,中间含有数量不固定的move事件,最终以up事件结束。
理解了这三个方法的工做过程也就理解了事件的分发机制。伪代码表示三个方法的关系以下:
public boolean dispatchTouchEvent(MotionEvent ev){ boolean consume=false; if(onInterceptTouchEvent(ev)){ consume=onTouchEvent(ev); }else{ consume=child.dispatchTouchEvent(ev); } return consume; }
具体传递规则:对于一个根ViewGroup来讲,点击事件产生之后,首先会传递给它,这时它的dispatchTouchEvent就会被调用,若是这个ViewGroup的onInterceptTouchEvent方法返回true就表示它要拦截当前事件,接着事件就会交给这个ViewGroup处理,即它的onTouchEvent方法就会被调用,若是这个ViewGroup的onInterceptTouchEvent返回false就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的dispatchTouchEvent方法就会被调用,如此反复直到事件被最终处理。
当一个点击事件产生后,它的传递过程遵循以下顺序:Activity->Window->View,即事件老是先传递给Activity,Activity在传递给Window,最后Window在传递给顶级View。顶级View接收到事件后,就会按照事件分发机制去分发事件。考虑一种状况,若是一个View的onTouchEvent返回false,那么它的父容器的onTouchEvent将会被调用,依此类推。若是所用的元素都不处理这个事件,那么这个事件将会最终传递给Activity处理,即Activity的onTouchEvent方法被调用。
几个重要结论:
正常状况下,一个事件序列只能被一个VIew拦截且消耗,由于一旦一个元素拦截了某个事件,那么同一个事件序列内的全部事件都会直接交给它处理,所以同一个事件序列中的事件不能分别有两个View同时处理,可是经过特殊手段能够作到,好比一个View将本该本身处理的事件经过onTouchEvent强行传递给其余View处理。
某个View一旦决定拦截,那么这一个事件序列都只能由它处理,而且它的onInterceptTouchEvent不会再被调用。
某个View一旦开始处理事件,若是它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其余事件都不会再交给它处理,而且事件将从新交由它的父元素去处理,即父元素的onTouchEvent会被调用。意思就是事件一旦交给一个View处理,那么它就必须消耗掉,不然同一事件序列中剩下的事件就再也不交给它来处理了。
若是View不消耗出ACTION_DOWN之外的其余事件,那么这个点击事件会消失,最终这些消失的点击事件会传递给Activity处理。
ViewGroup默认不拦截任何事件。
View没有onInterceptTouchEvent方法,一旦有事件传递给它,那么它的onTouchEvent方法就会被调用。
View的OnTouchEvent方法默认都会消耗事件(返回true)。
View的enable属性不影响onTouchEvent的默认返回值。
onClick会发生的前提是当前View是可点击的,而且它收到了down和up的事件。
事件的传递过程是由外向内的,即事件老是项传递给父元素,而后再由父元素分发给子View,经过requestDisallowInterceptTouchEvent方法能够在子元素中干预父元素的事件分发过程,可是ACTION_DOWN事件除外。(requestDisallowInterceptTouchEvent方法主要设置父元素中的FLAG_DISALLOW_INTERCEPT标记位,一旦设置后,ViewGroup将没法拦截除了ACTION_DOWN之外的其余点击事件)
3.5 View的滑动冲突
常见的滑动冲突场景
解决滑动冲突的方式:外部拦截法和内部拦截法。
1 外部拦截法
点击事件都先通过父容器的拦截处理,若是父容器须要此事件就拦截,若是不须要此事件就不拦截。
实现方法主要是重写父容器的onInterceptTouchEvent方法,代码以下:
@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; }
2 内部拦截法
父容器不拦截任何事件,所用的事件都传递给子元素,若是子元素须要此事件就直接消耗掉,不然就交由父容器进行处理。须要配合requestDisallowInterceptTouchEvent方法才能正常工做,同时重写子元素的dispatchTouchEvent方法,代码以下:
子元素:
@Override
public boolean dispatchTouchEvent(MotionEvent event) { int x=(int)event.getX(); int y=(int)event.getY(); switch (event.getAction()){ case MotionEvent.ACTION_DOWN: getParent().requestDisallowInterceptTouchEvent(true); break; case MotionEvent.ACTION_MOVE: int deltaX=x-mLastX; int deltaY=y-mLastY; if(父容器须要当前点击事件){ getParent().requestDisallowInterceptTouchEvent(false); } break; case MotionEvent.ACTION_UP: break; default: break; } mLastX=x; mLastY=y; return super.dispatchTouchEvent(event); }
父元素:
@Override
public boolean onInterceptTouchEvent(MotionEvent event) { int action=event.getAction(); if (action==MotionEvent.ACTION_DOWN){ return false; }else{ return true; } }
以上就是滑动处理滑动冲突的典型代码,当面对不一样的滑动策略时(场景1,2,3)只须要修改里面的条件便可。