覆写过onTouchEvent的朋友知道,一般来说我们在该方法体内都会处理集中touch类型的事件,有ACTION_DOWN、ACTION_MOVE、ACTION_UP等,不过上面我们的例子中并没有移动,只是单纯的按下、抬起。因此,我们的触摸事件也只有按下、抬起,因此有2次touch事件,而action分别为0和1。我们看看MotionEvent中的一些变量定义吧:
public final class MotionEvent extends InputEvent implements Parcelable {
// 代码省略
public static final int ACTION_DOWN = 0; // 按下事件
public static final int ACTION_UP = 1; // 抬起事件
public static final int ACTION_MOVE = 2; // 手势移动事件
public static final int ACTION_CANCEL = 3; // 取消
// 代码省略
}
可以看到,代表按下的事件为0,抬起事件为1,也证实了我们上面所说的。
在看另外两个场景:
1、我们点击按钮外的区域,输出Log如下 :
08-31 03:04:45.408: D/(1560): ### activity dispatchTouchEvent
08-31 03:04:45.512: D/(1560): ### activity dispatchTouchEvent
2、我们在onTouch函数中返回true, 输出Log如下 :
以上两个场景为什么会这样呢 ? 我们继续往下看吧。
Android Touch事件分发
那么整个事件分发的流程是怎样的呢 ?
简单来说就是用户触摸手机屏幕会产生一个触摸消息,最终这个触摸消息会被传送到ViewRoot ( 看4.2的源码时这个类改成了ViewRootImpl )的InputHandler,ViewRoot是GUI管理系统与GUI呈现系统之间的桥梁,根据ViewRoot的定义,发现它并不是一个View类型,而是一个Handler。InputHandler是一个接口类型,用于处理KeyEvent和TouchEvent类型的事件,我们看看源码 :
public final class ViewRoot extends Handler implements ViewParent,
View.AttachInfo.Callbacks {
// 代码省略
private final InputHandler mInputHandler = new InputHandler() {
public void handleKey(KeyEvent event, Runnable finishedCallback) {
startInputEvent(finishedCallback);
dispatchKey(event, true);
}
public void handleMotion(MotionEvent event, Runnable finishedCallback) {
startInputEvent(finishedCallback);
dispatchMotion(event, true); // 1、handle 触摸消息
}
};
// 代码省略
// 2、分发触摸消息
private void dispatchMotion(MotionEvent event, boolean sendDone) {
int source = event.getSource();
if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
dispatchPointer(event, sendDone);<SPAN> </SPAN>// 分发触摸消息
} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
dispatchTrackball(event, sendDone);
} else {
// TODO
Log.v(TAG, "Dropping unsupported motion event (unimplemented): " + event);
if (sendDone) {
finishInputEvent();
}
}
}
// 3、通过Handler投递消息
private void dispatchPointer(MotionEvent event, boolean sendDone) {
Message msg = obtainMessage(DISPATCH_POINTER);
msg.obj = event;
msg.arg1 = sendDone ? 1 : 0;
sendMessageAtTime(msg, event.getEventTime());
}
@Override
public void handleMessage(Message msg) {<SPAN> </SPAN>// ViewRoot覆写handlerMessage来处理各种消息
switch (msg.what) {
// 代码省略
case DO_TRAVERSAL:
if (mProfile) {
Debug.startMethodTracing("ViewRoot");
}
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
break;
case DISPATCH_POINTER: { // 4、处理DISPATCH_POINTER类型的消息,即触摸屏幕的消息
MotionEvent event = (MotionEvent) msg.obj;
try {
deliverPointerEvent(event); // 5、处理触摸消息
} finally {
event.recycle();
if (msg.arg1 != 0) {
finishInputEvent();
}
if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!");
}
} break;
// 代码省略
}
// 6、真正的处理事件
private void deliverPointerEvent(MotionEvent event) {
if (mTranslator != null) {
mTranslator.translateEventInScreenToAppWindow(event);
}
boolean handled;
if (mView != null && mAdded) {
// enter touch mode on the down
boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN;
if (isDown) {
ensureTouchMode(true); // 如果是ACTION_DOWN事件则进入触摸模式,否则为按键模式。
}
if(Config.LOGV) {
captureMotionLog("captureDispatchPointer", event);
}
if (mCurScrollY != 0) {
event.offsetLocation(0, mCurScrollY); // 物理坐标向逻辑坐标的转换
}
if (MEASURE_LATENCY) {
lt.sample("A Dispatching TouchEvents", System.nanoTime() - event.getEventTimeNano());
}
// 7、分发事件,如果是窗口类型,则这里的mView对应的就是PhonwWindow中的DecorView,否则为根视图的ViewGroup。
handled = mView.dispatchTouchEvent(event);
// 代码省略
}
}
// 代码省略
}
经过层层迷雾,不管代码7处的mView是DecorView还是非窗口界面的根视图,其本质都是ViewGroup,即触摸事件最终被根视图ViewGroup进行分发!!!
我们就以Activity为例来分析这个过程,我们知道显示出来的Activity有一个顶层窗口,这个窗口的实现类是PhoneWindow, PhoneWindow中的内容区域是一个DecorView类型的View,这个View这就是我们在手机上看到的内容,这个DecorView是FrameLayout的子类,Activity的的dispatchTouchEvent实际上就是调用PhoneWindow的dispatchTouchEvent,我们看看源代码吧,进入Activity的dispatchTouchEvent函数 :
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) { // 1、调用的是PhoneWindow的superDispatchTouchEvent(ev)
return true;
}
return onTouchEvent(ev);
}
public void onUserInteraction() {
}
可以看到,如果事件为按下事件,则会进入到onUserInteraction()这个函数,该函数为空实现,我们暂且不管它。继续看,发现touch事件的分发调用了getWindow().superDispatchTouchEvent(ev)函数,getWindow()获取到的实例的类型为PhoneWindow类型,你可以在你的Activity类中使用如下方式查看getWindow()获取到的类型:
Log.d("", "### Activiti中getWindow()获取的类型是 : " + this.getWindow()) ;
输出:
08-31 03:40:17.036: D/(1688): ### Activiti中getWindow()获取的类型是 : com.android.internal.policy.impl.PhoneWindow@5287fe38
OK,废话不多说,我们还是继续看PhoneWindow中的superDispatchTouchEvent函数吧。
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
恩,调用的是mDecor的superDispatchTouchEvent(event)函数,这个mDecor就是我们上面所说的DecorView类型,也就是我们看到的Activity上���所有内容的一个顶层ViewGroup,即整个ViewTree的根节点。看看它的声明吧。
// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;
那么我继续看看DecorView到底是个什么玩意儿吧。
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
/* package */int mDefaultOpacity = PixelFormat.OPAQUE;
/** The feature ID of the panel, or -1 if this is the application's DecorView */
private final int mFeatureId;
private final Rect mDrawingBounds = new Rect();
private final Rect mBackgroundPadding = new Rect();
private final Rect mFramePadding = new Rect();
private final Rect mFrameOffsets = new Rect();
private boolean mChanging;
private Drawable mMenuBackground;
private boolean mWatchingForMenu;
private int mDownY;
public DecorView(Context context, int featureId) {
super(context);
mFeatureId = featureId;
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
final int keyCode = event.getKeyCode();
// 代码省略
return isDown ? PhoneWindow.this.onKeyDown(mFeatureId, event.getKeyCode(), event)
: PhoneWindow.this.onKeyUp(mFeatureId, event.getKeyCode(), event);
}
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
final Callback cb = getCallback();
return cb != null && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super
.dispatchTouchEvent(ev);
}
@Override
public boolean dispatchTrackballEvent(MotionEvent ev) {
final Callback cb = getCallback();
return cb != null && mFeatureId < 0 ? cb.dispatchTrackballEvent(ev) : super
.dispatchTrackballEvent(ev);
}
public boolean superDispatchKeyEvent(KeyEvent event) {
return super.dispatchKeyEvent(event);
}
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
public boolean superDispatchTrackballEvent(MotionEvent event) {
return super.dispatchTrackballEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return onInterceptTouchEvent(event);
}
// 代码省略
}