概述
在Android中,事件包括了点按、长按、拖拽、滑动等,这些事件才能让Android响应用户的各种操作。但是归根结底,所有的这些事件都是以如下三个部分作为基础的:
- ACTION_DOWN(按下)
- ACTION_MOVE(移动)
- ACTION_UP(抬起)
所有的操作事件首先必须执行ACTION_DOWN(按下)操作,之后所有的操作都是以按下操作为前提,当按下操作完成后,接下来可能是一段ACTION_MOVE然后ACTION_UP,或者直接ACTION_UP。
所有操作事件的执行顺序必须是:ACTION_DOWN -> ACTION_MOVE -> ACTION_UP。(是否包含ACTION_MOVE取决于用户手势中是否包含了移动操作)
本文基于Android2.2.3版本进行分析(主要是因为:2.2.3版本代码清晰简单一些,而且原理是通用的)
Activity事件传递
Touch事件的产生涉及到硬件和Linux内核部分,虽然我在硬件组,但是我也不想去深究这块内容。目前只需要知道Touch事件产生后,最先相应它的是Activity的dispatchTouchEvent方法。
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev);}
其中,onUserInteraction()是一个空方法,开发者可以根据自己的需求override这个方法,这个方法在一个Touch事件的周期中肯定是第一个被调用。
接着分析getWindow().superDispatchTouchEvent(ev),这个方法其实最终是调用了ViewGroup的dispatchTouchEvent方法。
小结:
通过上面的分析,我们至少可以知道,一个Touch事件首先经过Activity的dispatchTouchEvent方法处理,然后分配给了ViewGroup的dispatchTouchEvent方法。
ViewGroup响应Touch事件
重点就是分析dispatchTouchEvent()方法,2.2.3版本这个方法还算简单,添加中文注释的源码如下:
public boolean dispatchTouchEvent(MotionEvent ev) { final int action = ev.getAction(); final float xf = ev.getX(); final float yf = ev.getY(); // mScrollX代表视图起始坐标x轴方向偏移量 // mScrollY代表视图起始坐标y轴方法偏移量 final float scrolledXFloat = xf + mScrollX; final float scrolledYFloat = yf + mScrollY; final Rect frame = mTempRect; boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; // 处理初始的ACTION_DOWN事件 if (action == MotionEvent.ACTION_DOWN) { // 当行为为ACTION_DOWN时,应该将mMotionTarget置为null // 因为,ACTION_DOWN代表一个新的Touch事件 if (mMotionTarget != null) { mMotionTarget = null; } // 判断当前ViewGroup是否对touch事件进行拦截 if (disallowIntercept || !onInterceptTouchEvent(ev)) { ev.setAction(MotionEvent.ACTION_DOWN); // 从ViewGroup的子View中找到应该处理该touch事件的控件 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); // 判断当前的Touch事件是否位于child的矩形区域中 if (frame.contains(scrolledXInt, scrolledYInt)) { // 获取Touch事件相对于View控件的偏移 final float xc = scrolledXFloat - child.mLeft; final float yc = scrolledYFloat - child.mTop; ev.setLocation(xc, yc); child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT; // 调用child view的dispatchTouchEvent方法对Touch事件进行处理 if (child.dispatchTouchEvent(ev)) { // 设置touch事件之后的处理View mMotionTarget = child; return true; } } } } } } boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) || (action == MotionEvent.ACTION_CANCEL); if (isUpOrCancel) { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // 之前处理ACTION_DOWN的时候,已经确定了需要处理后续touch事件的子View final View target = mMotionTarget; // 这里说明点击的是ViewGroup的空白区域,这时的Touch事件就需要由ViewGroup自己来处理了 if (target == null) { ev.setLocation(xf, yf); // ViewGroup的父类是View,所以这里其实也是调用了View的dispatchTouchEvent方法 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); // 交给子View来处理Touch事件 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);}
由于ViewGroup默认的onInterceptTouchEvent的返回值为false,所以ViewGroup并不对TouchEvent进行拦截。
从代码中,我们可以看到,如果ViewGroup本身不对Touch事件进行拦截,则Touch事件最终是交给相应的子View去处理的。
TODO:补充流程图。
View响应Touch事件
从ViewGroup响应Touch事件的源码分析来看,ViewGroup在不拦截Touch事件的前提下,是将Touch事件分发给Child View实现的。假设这里的Child View是一个Button,那我们来研究一下Touch事件在View里的传递规则。
查看Button的源码,我们是找不到dispatchTouchEvent方法实现的。但是,由于android.widget.Button继承自android.widget.TextView->android.view.View,最终我们发现dispatchTouchEvent方法是在View类中实现的。源码如下:
public boolean dispatchTouchEvent(MotionEvent event) { if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) { return true; } return onTouchEvent(event);}
Android2.2.3版本,superDispatchTouchEvent方法实现非常简单,但是已经足够说明问题。接下来,我们逐个分析if语句中的代码实现。
第一,mOnTouchListener是如何赋值的?
解答:从View中我们能找到如下方法:
public void setOnTouchListener(OnTouchListener l) { mOnTouchListener = l;}
这个方法我们应该是非常熟悉,当开发者给控件注册touch事件响应对象的时候,mOnTouchListener就已经被赋值了。
第二,mOnTouchListener.onTouch()算是回调方法吗?
解答:必须算啊,我们在对控件注册onTouch事件对象的时候,都会重写onTouch方法,这里就是触发我们注册的ouTouch方法。并且,我们可以看到,如果我们在重写的onTouch方法中返回true,则该Touch事件就已经被处理完成,否则,就继续调用View的onTouchEvent()方法。
那接下来,我们就继续分析一下View的onTouchEvent()方法,源码如下:
public boolean onTouchEvent(MotionEvent event) { final int viewFlags = mViewFlags; // 被disable的View不响应Touch事件 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: 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(); } break; } }}
onTouchEvent()方法看起来还是挺复杂的。这里,我们关注重点,特别是对ACTION_UP的处理。其中,对ACTION_UP的处理最终会调用到PerformClick类的实例化。接下来,我们看一下该类是如何实例化的:
private final class PerformClick implements Runnable { public void run() { performClick(); }}public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); if (mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); mOnClickListener.onClick(this); return true; } return false;}
之前我们分析过mOnTouchListener是在onTouch事件赋值的,那mOnClickListener肯定是在onClick事件进行赋值的,所以这里是在ACTION_UP事件中回调了onClick方法。
“`
参考资料
- Android Deeper-Touch事件分发响应机制
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
版权声明:本文为博主原创文章,未经博主允许不得转载。