首先对Android点击事件分发流程进行一个大概的总结,然后再贴出伪代码进行分析。
一、当一个点击事件发生时,调用顺序如下:
1.事件最先传到Activity的dispatchTouchEvent()进行事件分发;三、前提:ViewGroup包含一个View,而且假设ViewGroup没有拦截DOWN事件,View 处理了DOWN事件(即DOWN事件传递到View的onTouchEvent方法,返回了true),但ViewGroup半路拦截了接下来的MOVE事件。
那么:在这后续到来的第一个MOVE事件中,ViewGroup的onInterceptTouchEvent方法返回true拦截该MOVE事件,但该事件并没有传递给ViewGroup的onTouchEvent方法;这个MOVE事件将会被系统变成一个CANCEL事件传递给View的onTouchEvent方法。后续又来了一个或几个MOVE或者UP事件,(不会再传递给ViewGroup的onInterceptTouchEvent方法),该MOVE、UP事件才会直接传递给ViewGroup的onTouchEvent()进行处理,View再也不会收到该事件列产生的后续事件。
所以自定义控件最好考虑一下CANCEL事件,可能与UP处理逻辑一致,不然滑动嵌套复杂时可能有隐藏的bug。
四、参考文章:2.http://blog.csdn.net/guolin_blog/article/details/9097463
下面进入伪代码分析阶段,请注意参考上面第(一)条总结的流程:
一、Activity
public class Activity {
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
/**
* Called whenever a key, touch, or trackball event is dispatched to the
* activity. Implement this method if you wish to know that the user has
* interacted with the device in some way while your activity is running.
* This callback and {@link #onUserLeaveHint} are intended to help
* activities manage status bar notifications intelligently; specifically,
* for helping activities determine the proper time to cancel a notfication.
*
*
All calls to your activity's {@link #onUserLeaveHint} callback will
* be accompanied by calls to {@link #onUserInteraction}. This
* ensures that your activity will be told of relevant user activity such
* as pulling down the notification pane and touching an item there.
*
*
Note that this callback will be invoked for the touch down action
* that begins a touch gesture, but may not be invoked for the touch-moved
* and touch-up actions that follow.
*
* @see #onUserLeaveHint()
*/
//该方法为空方法
//从注释得知:当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法
//所以onUserInteraction()主要用于屏保
public void onUserInteraction() {
}
/**
* Called when a touch screen event was not handled by any of the views
* under it. This is most useful to process touch events that happen
* outside of your window bounds, where there is no view to receive it.
*
* @param event The touch screen event being processed.
*
* @return Return true if you have consumed the event, false if you haven't.
* The default implementation always returns false.
*/
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
}
二、PhoneWindow
public class PhoneWindow extends Window {
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
//mDecor是DecorView的实例
// DecorView是视图的顶层view,继承自FrameLayout(即ViewGroup),是所有界面的父布局
}
}
三、DecorView
public class DecorView extends FrameLayout {
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
//DecorView继承自FrameLayout
//那么它的父类就是ViewGroup
//而super.dispatchTouchEvent(event)方法,其实就应该是ViewGroup的dispatchTouchEvent()
}
}
四、ViewGroup
/**
*5.0代码有变更,但是基本思路差不多,本代码是4.x的
*/
public class ViewGroup extends View {
//重写了view的dispatchTouchEvent()方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
//disallowIntercept默认是false,受子view控制,代表是否启用拦截onInterceptTouchEvent(),
//(android开发艺术探索中提出的内部拦截法用到了此标志位)
//onInterceptTouchEvent(ev)默认实现是false,就是不拦截事件,取反,进入for循环,事件分发给子view
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
for (View child : childrenViews) {
//如果点击事件位于该子view区域
if (child.contains(ev.getX(), ev.getY())) {
if (child.dispatchTouchEvent(ev)) {
mMotionTarget = child;
return true;
}
}
}
}
}
//如果没有子view消耗事件,或者点击了没有子view的空白处
//就会走到这里,从而通过super.dispatchTouchEvent(ev)调用到View类的onTouchEvent(ev)方法
//所以,如果重写onInterceptTouchEvent(ev),决定某条件下拦截点击事件,那么就要重写View的onTouchEvent(ev)方法进行
//点击事件的相关操作。ViewGroup类本身没有onTouchEvent(ev)方法,是继承自View的。
if (mMotionTarget == null) {
return super.dispatchTouchEvent(ev);
}
return true;
}
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
}
注意:
.Rect frame=new Rect();
child.getHitRect(frame);
frame.contains(x,y);
可以用如上代码判断某个坐标点、某个点击事件是否在某个控件的区域内。
五、View
public class View {
public boolean dispatchTouchEvent(MotionEvent event) {
//一般ENABLED都是true
//onTouch优先于onTouchEvent执行。如果在onTouch方法中通过返回true将事件消费掉,onTouchEvent将不会再执行。
//不可用view,即DISENABLED的view是不能响应mOnTouchListener事件的(也不响应onClickListener)
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
mOnTouchListener.onTouch(this, event)) {
return true;
}
//如果mOnTouchListener.onTouch()返回false,或者是ENABLED == false,才会走View的onTouchEvent(event)方法,
//并且View的onClickListener是在onTouchEvent(event)中被执行的。
return onTouchEvent(event);
}
//该onTouchEvent(event)方法是view的默认行为,可以通过重写该方法进行自定义处理逻辑。
//onClick的调用是在onTouchEvent(event)方法中的
public boolean onTouchEvent(MotionEvent event) {
//并不做具体处理,不响应onClickListener(也不响应mOnTouchListener); //2.如果view是DISABLED,并且不能clickable或者不能longclickble,那么那么view将不消费该事件,不响应onClickListener(也不响应mOnTouchListener); //3.如果view是DISABLED,并且不管能不能clickable或者能不能longclickble,都将不处理点击事件对应的逻辑,
// 但是分情况决定是否消费该事件,不响应onClickListener()(也不响应mOnTouchListener)。 return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)); }//如果该view是可clickble或者longclickble,(并且DISABLED == false )才能进入下面的switch判断
这样,将上述伪代码从头到尾逐步分析一遍,点击事件的分发流程就基本清楚了。
需要注意的是,该流程是系统view和viewgroup默认的处理方式,总体上dispatchTouchEvent()、onInterceptTouchEvent()、 onTouchEvent()方法的调用流程是不能改变的。但是在具体处理点击事件的逻辑时,可以通过重写ViewGroup的onInterceptTouchEvent()方法,来改变系统默认的事件拦截逻辑,比如决定什么条件下拦截事件,什么条件下不拦截事件;可以通过重写view的onTouchEvent()方法,来改变系统默认的事件相关处理逻辑,比如disable情况下怎么处理,是否响应或者什么条件下响应onTouchListener(或者onClickListner)等等。
ViewGroup的onTouchEvent()方法,在拦截事件自己处理的时候被执行;也可能是因为子view没有处理事件,子view的dispatchTouchEvent()返回false的时候会执行。
理论上dispatchTouchEvent()这个方法也是可以被重写的,因为它是public并且不是final方法,只不过一般不建议重写该方法,因为这样处理起来会很复杂,只根据条件重写ViewGroup的onInterceptTouchEvent()或者onTouchEvent()方法即可。
另外还有两张图片进行总结,图片是从http://www.jianshu.com/p/38015afcdb58网站拷贝出来的,感谢图片原创作者。
好了,android事件分发流程基本讲完了。