涉及到的内容
1.view的位置参数
2.view事件处理工具类
3.view事件分发
4.简单view冲突处理
View的位置参数
在android的坐标体系中,x的右为正方向 y的下为正方向
view的位置如下图
width = right – left
height = bottom – top
3.0之后,View增加了参数:x, y,translationX, translationY,其中x和y是View左上角的坐标,而translationX和translationY是View左上角相对于父容器的偏移量。
X = left + translationX;
Y =right + translationY;
View在平移的过程中,top和left表示的是原始左上角的位置信息,他们的值不会发生改变,而x,y, translationX,translationY的值会随着变化。
需要注意的是这些坐标都是相对于View的父容器来说的
示例:
在button调用
button1.setTranslationX(100);
button1.setTranslationY(100);
之后,打印log
10-11 09:58:21.642:E/GesturetestActivity(1724): 调用之前:
10-11 09:58:21.642:E/GesturetestActivity getX(1724): 0.0
10-11 09:58:21.642:E/GesturetestActivity getY(1724): 0.0
10-11 09:58:21.642:E/GesturetestActivity getLeft(1724): 0
10-11 09:58:21.642:E/GesturetestActivity getTop(1724): 0
10-11 09:58:21.642: E/GesturetestActivity getTranslationX(1724): 0.0
10-11 09:58:21.642:E/GesturetestActivity getTranslationY(1724): 0.0
10-11 09:58:21.642:E/GesturetestActivity(1724): 调用之后:
10-11 09:58:21.642:E/GesturetestActivity getX(1724): 100.0
10-11 09:58:21.642: E/GesturetestActivity getY(1724): 100.0
10-11 09:58:21.642:E/GesturetestActivity getLeft(1724): 0
10-11 09:58:21.642:E/GesturetestActivity getTop(1724): 0
10-11 09:58:21.642:E/GesturetestActivity getTranslationX(1724): 100.0
10-11 09:58:21.642: E/GesturetestActivity getTranslationY(1724): 100.0
后话:
但是View动画和ScrollTo或者ScrollBy的方式不会改变View的x,y, translationX,translationY。
因为
1. scrollTo/ ScrollBy作用于父容器的,而View的这些属性值也是基于父容器的。
2.View动画移动的是View的影像
View事件处理工具类
MotionEvent
系统提供获取触摸事件的两组方法:
getX/getY 返回的是相对于当前View左上角的x和y的坐标
getRawX/getRawY 返回的是相对于手机屏幕左上角的x和y的坐标
ViewConfiguration
这里面所有的方法基本都是用来获取常量数据的,没有什么业务操作。
ViewConfiguration这个类主要定义了UI中所使用到的标准常量,像超时、尺寸、距离,如果我们需要得到这些常量的数据,我们就可以通过这个类来获取,具体方法如下:
1、获取ViewConfiguration对象,由于ViewConfiguration的构造方法为私有的,只能通过这个静态方法来获取到该对象。
ViewConfiguration configure =ViewConfiguration.get(context);
2、通过该对象调用相关的函数,将返回相关的常量数据。
http://www.aichengxu.com/view/65180 ViewConfiguration类
VelocityTracker
速度测量
用于测量手指在滑动过程中的速度,包括水平和竖直方向的速度。
mVelocityTracker =VelocityTracker.obtain();//获得VelocityTracker类实例
mVelocityTracker.addMovement(ev);//将事件加入到VelocityTracker类实例中
velocityTracker.computeCurrentVelocity(1000);//设置units的值为1000,意思为一秒时间内运动了多少个像素
velocityTracker.getXVelocity()
velocityTracker.getYVelocity()
velocityTracker.clear();
velocityTracker.recycle();
GestureDetector
手势检测
用于辅助检测用户的单击,滑动,长按,双击等事件。
Eg:
privatevoidinitGes(){
gestureDetector =newGestureDetector(this,newGestureDetector.SimpleOnGestureListener(){
private Screen screen;
@Override
public boolean onFling(MotionEvent e1,MotionEvent e2,
float velocityX, float velocityY) {
screen = GestureUtils.getScreenPix(GesturetestActivity.this);
int x_ = (int) (e2.getX() -e1.getX());
int y_ = (int) (e2.getY() -e1.getY());
int width_s = screen.widthPixels /4;
int height_s = screen.heightPixels/4;
int x_abs = Math.abs(x_);
int y_abs = Math.abs(y_);
//在水平方向移动的距离大于在竖直方向移动的距离
if(x_abs > y_abs){
//水平方向移动的距离大于宽度的1/4
if(x_abs > width_s){
if(x_>0) show("向右") ;elseshow("向左");
}
}else{
if(y_abs>height_s){
if(y_>0) show("向下") ;elseshow("向上");
}
}
return true;
}
});
}
@Override
publicbooleanonTouchEvent(MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
GestureDetector我们在实际开发中可以完全不使用,而自己在View的onTouchEvent方法中实现所需的监听。
具体使用哪种方法处理事件,看实际情况
View事件分发
所谓点击事件的事件分发,其实就是对MotionEvent事件的分发过程,即当一个MotionEvent产生了之后,系统需要把这个事件传递给一个具体的View,而这个传递的过程就是事件的分发过程。
由三个很重要的方法完成:
dispatchTouchEvent 进行事件分发,如果事件能够传递给当前view,那么此方法一定会被调用,返回结果受当前View的onTouchEvent和下级的dispatchTouchEvent影响,表示是否消耗当前事件。
onInterceptTouchEvent 在dispatchtouchEvent内部调用,用来判断是否拦截某个事件,如果当前View拦截了某个事件,那么过一个事件序列当中,此方法不会被再次调用,返回结果表示是否拦截当前的事件。
onTouchEvent 在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前View无法再次接收到事件。
他们的关系可以使用如下伪代码表示:
Public boolean dispatchTouchEvent(MotionEvent ev){
booleanconsume = false;
if(onInterceptTouchEvent(ev)){
consume= onTouchEvent(ev)
}else{
consume = child.dispatchTouchEvent();
}
return consume;
}
ViewGroup接收到一个事件,首先调用dispatchTouchEvent方法,然后调用onInterceptTouchEvent方法,如果onInterceptTouchEvent返回true,ViewGroup调用自己的OnTouchEvent事件将不会传给子View。如果onInterceptTouchEvent返回false,子View的dispatchTouchEvent方法被调用。
在一个View的内部,如果设置了OnTouchListener,那么OntouchListener中的onTouch方法会被回调。如果onTouch返回true,那么onTouchEvent将不会被调用。即给View设置的OnClickListener的onclick将不会被调用。所以OnTouchListener的优先级要高于 onTouchEvent。
事件传递顺序是:ActivityWindow View子View………….如果没有view处理这个事件,事件将会交给Activity处理。
注:一下这一部分关于事件分发的分析来自于hongyang的博客
1. /**
2. * Pass the touch screen motion event down to the target view, or this
3. * view if it is the target.
4. *
5. * @param event The motion event to be dispatched.
6. * @return True if the event was handled by the view, false otherwise.
7. */
8. public boolean dispatchTouchEvent(MotionEvent event) {
9. if (!onFilterTouchEventForSecurity(event)) {
10. return false;
11. }
12.
13. if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
14. mOnTouchListener.onTouch(this, event)) {
15. return true;
16. }
17. return onTouchEvent(event);
18. }
首先判断mOnTouchListener不为null,并且view是enable的状态,然后 mOnTouchListener.onTouch(this,event)返回true,这三个条件如果都满足,直接return true ; 也就是下面的onTouchEvent(event)不会被执行了
如果我们设置了setOnTouchListener,并且return true,那么View自己的onTouchEvent就不会被执行了
ViewGroup
1、ACTION_DOWN中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则找到包含当前x,y坐标的子View,赋值给mMotionTarget,然后调用 mMotionTarget.dispatchTouchEvent
2、ACTION_MOVE中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则直接调用mMotionTarget.dispatchTouchEvent(ev)
3、ACTION_UP中,ViewGroup捕获到事件,然后判断是否拦截,如果没有拦截,则直接调用mMotionTarget.dispatchTouchEvent(ev)
当然了在分发之前都会修改下坐标系统,把当前的x,y分别减去child.left和 child.top,然后传给child;
默认是不拦截的,即返回false;如果你需要拦截,只要return true就行了,这要该事件就不会往子View传递了,并且如果你在DOWN retrun true ,则DOWN,MOVE,UP子View都不会捕获事件;如果你在MOVE return true , 则子View在MOVE和UP都不会捕获事件。
原因很简单,当onInterceptTouchEvent(ev) return true的时候,会把mMotionTarget置为null ;
如果ViewGroup的onInterceptTouchEvent(ev)当ACTION_MOVE时return true ,即拦截了子View的MOVE以及UP事件;
此时子View希望依然能够响应MOVE和UP时该咋办呢?
Android给我们提供了一个方法:requestDisallowInterceptTouchEvent(boolean)用于设置是否允许拦截,我们在子View的dispatchTouchEvent中直接这么写:
1. @Override
2. public boolean dispatchTouchEvent(MotionEvent event)
3. {
4. getParent().requestDisallowInterceptTouchEvent(true);
5. int action = event.getAction();
6.
7. switch (action)
8. {
9. case MotionEvent.ACTION_DOWN:
10. Log.e(TAG, "dispatchTouchEvent ACTION_DOWN");
11. break;
12. case MotionEvent.ACTION_MOVE:
13. Log.e(TAG, "dispatchTouchEvent ACTION_MOVE");
14. break;
15. case MotionEvent.ACTION_UP:
16. Log.e(TAG, "dispatchTouchEvent ACTION_UP");
17. break;
18.
19. default:
20. break;
21. }
22. return super.dispatchTouchEvent(event);
23. }
getParent().requestDisallowInterceptTouchEvent(true); 这样即使ViewGroup在MOVE的时候return true,子View依然可以捕获到MOVE以及UP事件。
因为ViewGroup源码中:
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
当我们把disallowIntercept设置为true时,!disallowIntercept直接为false,于是拦截的方法体就被跳过了
ACTION_DOWN的时候,子View.dispatchTouchEvent(ev)返回的为false ;
如果你仔细看了,你会注意到ViewGroup的dispatchTouchEvent(ev)的ACTION_DOWN代码是这样的
1. if (child.dispatchTouchEvent(ev)) {
2. // Event handled, we have a target now.
3. mMotionTarget = child;
4. return true;
5. }
只有在child.dispatchTouchEvent(ev)返回true了,才会认为找到了能够处理当前事件的View,即mMotionTarget= child;
但是如果返回false,那么mMotionTarget依然是null
mMotionTarget 为null会咋样呢?
其实ViewGroup也是View的子类,如果没有找到能够处理该事件的子View,或者干脆就没有子View;
那么,它作为一个View,就相当于View的事件转发了~~直接super.dispatchTouchEvent(ev);
源码是这样的:
1. final View target = mMotionTarget;
2. if (target == null) {
3. // We don't have a target, this means we're handling the
4. // event as a regular view.
5. ev.setLocation(xf, yf);
6. if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
7. ev.setAction(MotionEvent.ACTION_CANCEL);
8. mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
9. }
10. return super.dispatchTouchEvent(ev);
11. }
我们没有一个能够处理该事件的目标元素,意味着我们需要自己处理~~~就相当于传统的View~
1、如果ViewGroup找到了能够处理该事件的View,则直接交给子View处理,自己的onTouchEvent不会被触发;
2、可以通过复写onInterceptTouchEvent(ev)方法,拦截子View的事件(即returntrue),把事件交给自己处理,则会执行自己对应的onTouchEvent方法
3、子View可以通过调用getParent().requestDisallowInterceptTouchEvent(true); 阻止ViewGroup对其MOVE或者UP事件进行拦截;
结合简书上Kelin 的文章,自己重新画了一遍流程
一张图终结:
View简单冲突处理
eg:
当出现上下,左右滑动时
在父View中判断是否拦截
@Override
publicbooleanonInterceptTouchEvent(MotionEvent event) {
boolean intercepted = false;
int x = (int) event.getX();
int y = (int) event.getY();
switch (event.getAction()) {
case MotionEvent.ACTION_DOWN: {
intercepted = false;
if (!mScroller.isFinished()) {
mScroller.abortAnimation();
intercepted = true;
}
break;
}
case MotionEvent.ACTION_MOVE: {
int deltaX = x - mLastXIntercept;
int deltaY = y - mLastYIntercept;
if (Math.abs(deltaX) > Math.abs(deltaY)){
intercepted = true;
} else {
intercepted = false;
}
break;
}
case MotionEvent.ACTION_UP: {
intercepted = false;
break;
}
default:
break;
}
Log.d(TAG,"intercepted="+ intercepted);
mLastX = x;
mLastY = y;
mLastXIntercept = x;
mLastYIntercept = y;
return intercepted;
}
参考:
1.图解 Android 事件分发机制
2.Hongyang的一遍文章(抱歉不记得网址了)
3.任玉刚的《andorid开发艺术探索》