Android的事件分发机制
来源:互联网 发布:怎么js调用方法 编辑:程序博客网 时间:2024/05/17 07:01
点击事件的传递规则
同个事件:
是指手指触摸屏幕的那一刻起,到手指离开屏幕的那一刻结束,在整个过程中所产生的一系列事件,这个事件序列以 down 事件开始,中间含有数量不定的move事件,最终以 up 事件结束。
事件传递规则:
当一个点击事件发生之后,传递过程遵循如下顺序:Activity -> Window -> View
对于跟ViewGroup来说,点击事件产生后,首先会传递给它,这时它的 dispatchTouchEvent 方法就会被调用,如果这时ViewGroup的 onInterceptTouchEvent 方法返回 true 就表示它要拦截当前事件,接着事件就会交给ViewGroup处理,即它的 onTouchEvent 方法就会被调用;如果这个ViewGroup的 onInterceptTouchEvent 方法返回 false, 就表示它不拦截当前事件,这时当前事件就会继续传递给它的子元素,接着子元素的ispatchTouchEvent 方法就会被调用,如此反复直到事件被最终处理。如果一个view的onTouchEvent方法返回false,那么它的父容器的onTouchEvent方法将会被调用,依此类推,如果所有的元素都不处理这个事件,那么这个事件将会最终传递给Activity处理,即Activity的onTouchEvent方法会被调用。
伪代码:
public boolean dispatchTouchEvent(MotionEvent ev) { boolean consume = false; if (onInterceptTouchEvent(ev)) { consume = onTouchEvent(ev); } else { consume = child.dispatchTouchEvent(ev); } return consume;}
事件分发过程的三个重要方法:
1 . public boolean dispatchTouchEvent(MotionEvent ev)
用来进行事件的分发。如果事件能够传递给当前view,那么此方法一定会被调用,返回结果受当前view的onTouchEvent和下级view的dispatchTouchEvent方法的影响,表示是否消耗当前事件。
2 . public boolean onInterceptTouchEvent(MotionEvent ev)
在dispatchTouchEvent方法内部调用,用来判断是否拦截某个事件,如果当前view拦截了某个事件,那么在同一个事件序列当中,此方法不会再被调用,返回结果表示是否拦截当前事件。
ViewGroup中onInterceptTouchEvent方法默认返回false,即默认不拦截任何事件
View中没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就会被调用。
3 . public boolean onTouchEvent(MotionEvent event)
在dispatchTouchEvent方法中调用,用来处理点击事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件序列中,当前view无法再次接收到事件。
View中的onTouchEvent默认返回true,消耗事件,除非它是不可点击的(clickable和longClickable都为false)。View的longClickable默认是false的,clickable则不一定,Button默认是true,而TextView默认是false。
ViewGroup不设置监听事件的话,onTouchEvent默认返回false,ViewGroup设置监听事件的话,onTouchEvent返回true,消耗事件。
View的enable属性不影响onTouchEvent的默认返回值。哪怕一个view是disable状态的,只要它的clickable或者longClickable有一个是true,那么它的onTouchEvent就会返回true。
某个view一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回了false),那么同一事件序列中的其他事件都不会再交给它来处理,并且事件将重新交由它的父容器去处理,即调用父容器的onTouchEvent方法;如果它消耗ACTION_DOWN事件,但是不消耗其他类型事件,那么这个点击事件会消失,此时父元素的onTouchEvent方法并不会被调用,当前view依然可以收到后续的事件(即当前view的onTouchEvent方法会被调用),但是这些事件最后都会传递给Activity处理。(因为正常情况下,一个事件序列只能被一个view拦截并消耗,因为一旦某个元素拦截了某个事件,那么同一个事件序列内的所有事件都会直接交给它处理,并且该元素的onInterceptTouchEvent方法不会再被调用了。)
其它:
1 . 如果给一个view设置了OnTouchListener,那么OnTouchListener中的onTouch方法会被回调。这时事件如何处理还要看onTouch的返回值,如果返回false,则当前view的onTouchEvent方法会被调用;如果返回true,则onTouchEvent方法将不会被调用。在onTouchEvent方法中,如果当前view设置了OnClickListener,那么它的onClick方法会被调用,所以OnTouchListener的优先级比onTouchEvent高,OnClickListener的优先级最低。
2 . 事件传递过程总是先传递给父元素,然后再由父元素分发给子view,通过requestDisallowInterceptTouchEvent方法可以在子元素中干预父元素的事件分发过程,但是ACTION_DOWN事件除外,即对于ACTION_DOWN时,ViewGroup会调用自己的onInterceptTouchEvent方法来询问是否要拦截事件。
VIew的滑动冲突
如何根据坐标得到滑动的方向:
根据滑动距离和水平方向形成的夹角;
根据水平和竖直方向滑动的距离差;
根据水平和竖直方向滑动的的速度差等
滑动冲突的解决方式:
1 . 外部拦截法:点击事件都先经过父容器的拦截处理,如果父容器需要此事件就拦截,如果不需要就不拦截。外部拦截法需要重写父容器的onInterceptTouchEvent方法,在内部做相应的拦截即可。
父容器的onInterceptTouchEvent方法伪代码:
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: { int deltaX = x - mLastXIntercept; int deltaY = y - mLastYIntercept; if (父容器需要当前点击事件) { intercepted = true; } else { intercepted = false; } break; } case MotionEvent.ACTION_UP: { intercepted = false; break; } default: break; } mLastXIntercept = x; mLastYIntercept = y; return intercepted;}
事件分发过程:
父容器的onInterceptTouchEvent方法,对于down事件和move父容器不需要当前点击得事件,返回false,才能传到子元素,因为如果返回true,一旦某个元素拦截了某个事件,那么同一个事件序列内的所有事件都会直接交给它处理,并且该元素的onInterceptTouchEvent方法不会再被调用了
如果父容器需要某类点击事件,返回true,拦截事件
对于up,返回false,因为在父容器不需要任何类点击事件时,如果在up事件返回true,会导致子元素无法接收到up事件;在父容器需要某类点击事件时,在up事件返回false,不会影响onTouchEvent方法接受到up事件(因为之前move事件,在父容器需要某类点击事件返回true,如果返回true,一旦某个元素拦截了某个事件,那么同一个事件序列内的所有事件都会直接交给它处理,并且该元素的onInterceptTouchEvent方法不会再被调用了)
2 . 内部拦截法:父容器不拦截任何事件,所有的事件都传递给子元素,如果子元素需要此事件就直接消耗掉,否则就交由父容器来处理。这种方法和Android中的事件分发机制不一致,需要配合requestDisallowInterceptTouchEvent方法才能正常工作。
子元素的dispatchTouchEvent方法伪代码:
public boolean dispatchTouchEvent(MotionEvent event) { int x = (int) event.getX(); int y = (int) event.getY(); switch (event.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(event);}
父容器的onInterceptTouchEvent方法伪代码
public boolean onInterceptTouchEvent(MotionEvent event){ int action = event.getAction(); if(action == MotionEvent.ACTION_DOWN){ return false; }else{ return true; }}
事件分发过程:
父容器的onInterceptTouchEvent方法,对于down,返回false,才能传到子元素,因为如果返回true,一旦某个元素拦截了某个事件,那么同一个事件序列内的所有事件都会直接交给它处理,并且该元素的onInterceptTouchEvent方法不会再被调用了
子元素的dispatchTouchEvent方法,对于dowm,调用Parent().requestDisallowInterceptTouchEvent(true),设置了FLAG_DISALLOW_INTERCEPT,父容器将无法拦截除了down以外的事件(即down以外的事件,父容器不会再调用onInterceptTouchEvent方法),所以down以后的其他事件传到子元素。当父容器需要某类点击事件时,调用Parent().requestDisallowInterceptTouchEvent(false),对FLAG_DISALLOW_INTERCEPT重置,父容器就可以继续调用它自己的onInterceptTouchEvent方法,所以在down以外的事件,父容器的onInterceptTouchEvent方法返回true,拦截事件。
其它:ViewGroup在事件分发时,如果是down事件就会重置FLAG_DISALLOW_INTERCEPT这个标志位,导致之前子元素设置的标志位无效,所以,如果我们想提前处理所以的点,要选择dispatchTouchEvent方法,前提事件能传递到当前的ViewGroup.
参考:Android开发艺术探索
- Android的事件分发机制
- Android事件的分发机制
- Android的事件分发机制
- Android 的事件分发机制
- android 事件的分发机制
- Android 的事件分发机制
- Android事件的分发机制
- Android的事件分发机制
- android事件的分发机制
- Android的事件分发机制
- android的事件分发机制
- Android的事件分发机制
- Android的事件分发机制
- Android的事件分发机制
- Android事件的分发机制
- Android 的事件分发机制
- Android 事件的分发机制
- Android 事件的分发机制
- Selector在drawable和color目录下使用的规范
- hdu5781 ATM Mechine 概率dp
- java基础之获取一定范围的随机数字14
- 杭电oj---2002 计算球体积
- 1004. 成绩排名
- Android的事件分发机制
- LTE 网元
- 有关opencv的学习(9)—计算图像直方图
- 算法中的 log
- bzoj 1863 二分+递推
- Linux Ptrace 详解
- 1005. 继续(3n+1)猜想
- poj2104 K-th Number
- C程序更改UNIX系统时间