尽管网络上已经有很多关于这个话题的优秀文章了,但还是写了这篇文章,主要还是为了加强自己的记忆吧,自己过一遍总比看别人的分析要深刻得多,那就走起吧。
简单示例
先看一个示例 :
布局文件 :
- <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:tools="http://schemas.android.com/tools"
- android:id="@+id/container"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:layout_gravity="center"
- tools:context="com.example.touch_event.MainActivity"
- tools:ignore="MergeRootFrame" >
-
- <Button
- android:id="@+id/my_button"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:text="@string/hello_world" />
-
- </FrameLayout>
MainActivity文件:
- public class MainActivity extends Activity {
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
- Button mBtn = (Button) findViewById(R.id.my_button);
- mBtn.setOnTouchListener(new OnTouchListener() {
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- Log.d("", "### onTouch : " + event.getAction());
- return false;
- }
- });
- mBtn.setOnClickListener(new OnClickListener() {
-
- @Override
- public void onClick(View v) {
- Log.d("", "### onClick : " + v);
- }
- });
-
- }
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- Log.d("", "### activity dispatchTouchEvent");
- return super.dispatchTouchEvent(ev);
- }
- }
当用户点击按钮时会输出如下Log,
08-31 03:03:56.116: D/(1560): ### activity dispatchTouchEvent
08-31 03:03:56.116: D/(1560): ### onTouch : 0
08-31 03:03:56.196: D/(1560): ### activity dispatchTouchEvent
08-31 03:03:56.196: D/(1560): ### onTouch : 1
08-31 03:03:56.196: D/(1560): ### onClick : android.widget.Button{52860d98 VFED..C. ...PH... 0,0-1080,144 #7f05003d app:id/my_button}
我们可以看到首先执行了Activity中的dispatchTouchEvent方法,然后执行了onTouch方法,然后再是 dispatchTouchEvent --> onTouch, 最后才是执行按钮的点击事件。这里我们可能有个疑问,为什么dispatchTouchEvent和onTouch都执行了两次,而onClick才执行 了一次 ? 为什么两次的Touch事件的action不一样,action 0 和 action 1到底代表了什么 ?
覆写过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 dispatchTouchEvent08-31
03:04:45.512: D/(1560): ### activity dispatchTouchEvent
2、我们在onTouch函数中返回true, 输出Log如下 :
08-31 03:06:04.764: D/(1612): ### activity dispatchTouchEvent
08-31 03:06:04.764: D/(1612): ### onTouch : 0
08-31 03:06:04.868: D/(1612): ### activity dispatchTouchEvent
08-31 03:06:04.868: D/(1612): ### onTouch : 1
以上两个场景为什么会这样呢 ? 我们继续往下看吧。
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);
- }
- };
-
-
- private void dispatchMotion(MotionEvent event, boolean sendDone) {
- int source = event.getSource();
- if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
- dispatchPointer(event, sendDone);
- } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
- dispatchTrackball(event, sendDone);
- } else {
-
- Log.v(TAG, "Dropping unsupported motion event (unimplemented): " + event);
- if (sendDone) {
- finishInputEvent();
- }
- }
- }
-
- 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) {
-
- switch (msg.what) {
-
- case DO_TRAVERSAL:
- if (mProfile) {
- Debug.startMethodTracing("ViewRoot");
- }
-
- performTraversals();
-
- if (mProfile) {
- Debug.stopMethodTracing();
- mProfile = false;
- }
- break;
-
- case DISPATCH_POINTER: {
-
- MotionEvent event = (MotionEvent) msg.obj;
- try {
- deliverPointerEvent(event);
- } finally {
- event.recycle();
- if (msg.arg1 != 0) {
- finishInputEvent();
- }
- if (LOCAL_LOGV || WATCH_POINTER) Log.i(TAG, "Done dispatching!");
- }
- } break;
-
- }
-
- private void deliverPointerEvent(MotionEvent event) {
- if (mTranslator != null) {
- mTranslator.translateEventInScreenToAppWindow(event);
- }
- boolean handled;
- if (mView != null && mAdded) {
-
- boolean isDown = event.getAction() == MotionEvent.ACTION_DOWN;
- if (isDown) {
- ensureTouchMode(true);
- }
- 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());
- }
-
- 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)) {
-
- 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的根节点。看看它的声明吧。
-
- private DecorView mDecor;
DecorView
那么我继续看看DecorView到底是个什么玩意儿吧。
- private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
- int mDefaultOpacity = PixelFormat.OPAQUE;
-
-
- 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);
- }
-
- }
可以看到,DecorView继承自FrameLayout, 它对于touch事件的分发( dispatchTouchEvent )、处理都是交给super类来处理,也就是FrameLayout来处理,我们在FrameLayout中没有看到相应的实现,那继续跟踪到 FrameLayout的父类,即ViewGroup,我们看到了dispatchTouchEvent的实现,那我们就先看ViewGroup (Android 2.3 源码)是如何进行事件分发的吧。
ViewGroup的Touch事件分发
-
-
-
- @Override
- public boolean dispatchTouchEvent(MotionEvent ev) {
- if (!onFilterTouchEventForSecurity(ev)) {
- return false;
- }
-
- final int action = ev.getAction();
- final float xf = ev.getX();
- final float yf = ev.getY();
- final float scrolledXFloat = xf + mScrollX;
- final float scrolledYFloat = yf + mScrollY;
- final Rect frame = mTempRect;
-
- boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
-
- if (action == MotionEvent.ACTION_DOWN) {
- if (mMotionTarget != null) {
-
-
-
-
- mMotionTarget = null;
- }
-
-
- if (disallowIntercept || !onInterceptTouchEvent(ev))
-
-
- ev.setAction(MotionEvent.ACTION_DOWN);
-
-
- final int scrolledXInt = (int) scrolledXFloat;
- final int scrolledYInt = (int) scrolledYFloat;
- final View[] children = mChildren;
- final int count = mChildrenCount;
-
- for (int i = count - 1; i >= 0; i--)
-
- final View child = children[i];
- if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
- || child.getAnimation() != null) {
- child.getHitRect(frame);
-
- if (frame.contains(scrolledXInt, scrolledYInt))
-
-
- final float xc = scrolledXFloat - child.mLeft;
- final float yc = scrolledYFloat - child.mTop;
- ev.setLocation(xc, yc);
- child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- if (child.dispatchTouchEvent(ev))
-
-
- mMotionTarget = child;
- return true;
- }
-
-
-
- }
- }
- }
- }
- }
-
- boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
- (action == MotionEvent.ACTION_CANCEL);
-
- if (isUpOrCancel) {
-
-
- mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
- }
-
-
-
- final View target = mMotionTarget;
- if (target == null) {
-
-
- ev.setLocation(xf, yf);
- if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
- ev.setAction(MotionEvent.ACTION_CANCEL);
- mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- }
- return super.dispatchTouchEvent(ev);
- }
-
-
-
- if (!disallowIntercept && onInterceptTouchEvent(ev)) {
- final float xc = scrolledXFloat - (float) target.mLeft;
- final float yc = scrolledYFloat - (float) target.mTop;
- mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- ev.setAction(MotionEvent.ACTION_CANCEL);
- ev.setLocation(xc, yc);
- if (!target.dispatchTouchEvent(ev)) {
-
-
- }
-
- mMotionTarget = null;
-
-
-
- return true;
- }
-
- if (isUpOrCancel) {
- mMotionTarget = null;
- }
-
-
-
- final float xc = scrolledXFloat - (float) target.mLeft;
- final float yc = scrolledYFloat - (float) target.mTop;
- ev.setLocation(xc, yc);
-
- if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
- ev.setAction(MotionEvent.ACTION_CANCEL);
- target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
- mMotionTarget = null;
- }
-
- return target.dispatchTouchEvent(ev);
- }
这个函数代码比较长,我们只看上文中标注的几个关键点。首先在代码1处可以看到一个条件判断,如果disallowIntercept 和!onInterceptTouchEvent(ev)两者有一个为true,就会进入到这个条件判断中。disallowIntercept是指是否 禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。 那么当第一个值为false的时候就会完全依赖第二个值来决定是否可以进入到条件判断的内部,第二个值是什么呢?onInterceptTouchEvent就是ViewGroup对事件进行拦截的一个函数,返回该函数返回false则表示不拦截事件,反之则表示拦截。第二个条件是是 对onInterceptTouchEvent方法的返回值取反,也就是说如果我们在onInterceptTouchEvent方法中返回false, 就会让第二个值为true,从而进入到条件判断的内部,如果我们在onInterceptTouchEvent方法中返回true,就会让第二个值的整体 变为false,从而跳出了这个条件判断。例如我们需要实现ListView滑动删除某一项的功能,那么可以通过在onInterceptTouchEvent返回true,并且在onTouchEvent中实现相关的判断逻辑,从而实现该功能。
进入代码1内部的if后,有一个for循环,遍历了当前ViewGroup下的所有子child view,如果触摸该事件的坐标在某个child view的坐标范围内,那么该child view来处理这个触摸事件,即调用该child view的dispatchTouchEvent。如果该child view是ViewGroup类型,那么继续执行上面的判断,并且遍历子view;如果该child view不是ViewGroup类型,那么直接调用的是View中的dispatchTouchEvent方法,除非这个child view的类型覆写了该方法。我们看看View中的dispatchTouchEvent函数:
View的Touch事件分发
-
-
-
-
-
-
-
- public boolean dispatchTouchEvent(MotionEvent event) {
- if (!onFilterTouchEventForSecurity(event)) {
- return false;
- }
-
- if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
- mOnTouchListener.onTouch(this, event)) {
- return true;
- }
- return onTouchEvent(event);
- }
该函数中,首先判断该事件是否符合安全策略,然后判断该view是否是enable的 ,以及是否设置了Touch Listener,mOnTouchListener即我们通过setOnTouchListener设置的。
-
-
-
-
- public void setOnTouchListener(OnTouchListener l) {
- mOnTouchListener = l;
- }
如果mOnTouchListener.onTouch(this, event)返回false则继续执行onTouchEvent(event);如果mOnTouchListener.onTouch(this, event)返回true,则表示该事件被消费了,不再传递,因此也不会执行onTouchEvent(event)。这也验证了我们上文中留下的场景2,当onTouch函数返回true时,点击按钮,但我们的点击事件没有执行。那么我们还是先来看看onTouchEvent(event)函数到底做了什么吧。
-
-
-
-
-
-
- public boolean onTouchEvent(MotionEvent event) {
- final int viewFlags = mViewFlags;
-
- if ((viewFlags & ENABLED_MASK) == DISABLED)
-
-
-
- return (((viewFlags & CLICKABLE) == CLICKABLE ||
- (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
- }
-
- if (mTouchDelegate != null) {
- if (mTouchDelegate.onTouchEvent(event)) {
- return true;
- }
- }
-
- if (((viewFlags & CLICKABLE) == CLICKABLE ||
- (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE))
-
- switch (event.getAction()) {
- case MotionEvent.ACTION_UP:
-
- boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;
- if ((mPrivateFlags & PRESSED) != 0 || prepressed) {
-
-
- boolean focusTaken = false;
- if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
- focusTaken = requestFocus();
-
- }
-
- if (!mHasPerformedLongPress) {
-
- removeLongPressCallback();
-
-
- if (!focusTaken) {
-
-
-
- if (mPerformClick == null) {
- mPerformClick = new PerformClick();
- }
- if (!post(mPerformClick))
-
- performClick();
-
- }
- }
- }
-
- if (mUnsetPressedState == null) {
- mUnsetPressedState = new UnsetPressedState();
- }
-
- if (prepressed) {
- mPrivateFlags |= PRESSED;
- refreshDrawableState();
- postDelayed(mUnsetPressedState,
- ViewConfiguration.getPressedStateDuration());
- } else if (!post(mUnsetPressedState)) {
-
- mUnsetPressedState.run();
- }
- removeTapCallback();
- }
- break;
-
- case MotionEvent.ACTION_DOWN:
- if (mPendingCheckForTap == null) {
- mPendingCheckForTap = new CheckForTap();
- }
- mPrivateFlags |= PREPRESSED;
- mHasPerformedLongPress = false;
- postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
- break;
-
- case MotionEvent.ACTION_CANCEL:
- mPrivateFlags &= ~PRESSED;
- refreshDrawableState();
- removeTapCallback();
- break;
-
- case MotionEvent.ACTION_MOVE:
- final int x = (int) event.getX();
- final int y = (int) event.getY();
-
-
- int slop = mTouchSlop;
- if ((x < 0 - slop) || (x >= getWidth() + slop) ||
- (y < 0 - slop) || (y >= getHeight() + slop)) {
-
- removeTapCallback();
- if ((mPrivateFlags & PRESSED) != 0) {
-
- removeLongPressCallback();
-
-
- mPrivateFlags &= ~PRESSED;
- refreshDrawableState();
- }
- }
- break;
- }
- return true;
- }
-
- return false;
- }
我们看到,在onTouchEvent函数中就是对ACTION_UP、ACTION_DOWN、ACTION_MOVE等几个事件进行处理,而最重要的 就是UP事件了,因为这个里面包含了对用户点击事件的处理,或者是说对于用户而言相对重要一点,因此放在了第一个case中。在ACTION_UP事件中 会判断该view是否enable、是否clickable、是否获取到了焦点,然后我们看到会通过post方法将一个PerformClick对象投递 给UI线程,如果投递失败则直接调用performClick函数执行点击事件。
-
-
-
-
-
-
-
-
-
-
- public boolean post(Runnable action) {
- Handler handler;
- if (mAttachInfo != null) {
- handler = mAttachInfo.mHandler;
- } else {
-
- ViewRoot.getRunQueue().post(action);
- return true;
- }
-
- return handler.post(action);
- }
我们看看PerformClick类吧。
- private final class PerformClick implements Runnable {
- public void run() {
- performClick();
- }
- }
可以看到,其内部就是包装了View类中的performClick()方法。再看performClick()方法:
-
-
-
-
-
-
-
-
- public void setOnClickListener(OnClickListener l) {
- if (!isClickable()) {
- setClickable(true);
- }
- mOnClickListener = l;
- }
-
-
-
-
-
-
-
- public boolean performClick() {
- sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
-
- if (mOnClickListener != null) {
- playSoundEffect(SoundEffectConstants.CLICK);
- mOnClickListener.onClick(this);
- return true;
- }
-
- return false;
- }
代码很简单,主要就是调用了mOnClickListener.onClick(this);方法,即执行用户通过setOnClickListener设置进来的点击事件处理Listener。
总结
用户触摸屏幕产生一个触摸消息,系统底层将该消息转发给ViewRoot ( ViewRootImpl ),ViewRoot产生一个DISPATCHE_POINTER的消息,并且在handleMessage中处理该消息,最终会通过 deliverPointerEvent(MotionEvent event)来处理该消息。在该函数中会调用mView.dispatchTouchEvent(event)来分发消息,该mView是一个 ViewGroup类型,因此是ViewGroup的dispatchTouchEvent(event),在该函数中会遍历所有的child view,找到该事件的触发的左边与每个child view的坐标进行对比,如果触摸的坐标在该child view的范围内,则由该child view进行处理。如果该child view是ViewGroup类型,则继续上一步的查找过程;否则执行View中的dispatchTouchEvent(event)函数。在View 的dispatchTouchEvent(event)中首先判断该控件是否enale以及mOnTouchListent是否为空,如果 mOnTouchListener不为空则执行mOnTouchListener.onTouch(event)方法,如果该方法返回false则再执行 View中的onTouchEvent(event)方法,并且在该方法中执行mOnClickListener.onClick(this, event) ;方法; 如果mOnTouchListener.onTouch(event)返回true则不会执行onTouchEvent方法,因此点击事件也不会被执行。
(bboyfeiyu) |