1.概括
Android的事件分发机制总结一句话就是:责任链模式,事件层层传递,直到被消费。其流程为:Activity->ViewGroup->View
2.事件的分发、拦截、消费
| 类型 | 事件 | Activity | ViewGroup | View |
|---|---|---|---|---|
| 事件分发 | dispatchTouchEvent | 有 | 有 | 有 |
| 事件拦截 | onInterceptTouchEvent | 无 | 有 | 无 |
| 事件处理 | onTouchEvent | 有 | 有 | 有 |
3.源码分析
- 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.
* 返回true事件才会被消费,返回false将不会触发任何事件
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
//第一步:当手指按下屏幕,调用onUserInteraction()方法
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//第二步:调用window的superDispatchTouchEvent()方法,如果返回true,则分发事件。
if (getWindow().superDispatchTouchEvent(ev)) {
//实际上这里调用的PhoneWindow的superDispatchTouchEvent()方法
//PhoneWindow是Window的唯一实现类,我们知道PhoneWindow中持有一个decorView,
//window.decorView是一个FrameLayout,它是内容视图的真正根布局,
//我们再来看看getWindow().superDispatchTouchEvent(ev)具体实现
/**
*@Override
*public boolean superDispatchKeyEvent(KeyEvent event) {
* //最终调用到了decorView的superDispatchKeyEvent()方法
* //从而分发到ViewGroup中
* return mDecor.superDispatchKeyEvent(event);
*}
*/
return true;
}
//否则调用Activity的onTouchEvent()消费事件
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.
*
* <p>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.
*
* <p>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正在运行的时候,如果你想知道用户和设备正在交互,可以实现这个方法。
* 不管什么时候,按键、触摸、trackball事件都会被分发到Activity
*/
public void onUserInteraction() {//空方法
}
- ViewGroup
ViewGroup的dispatchTouchEvent()方法代码非常多,下面咱们只看关键代码
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
------------------上面这段代码是检查是否需要拦截事件------------------
------------------核心代码-----------------------------
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
//子控件想在范围内接收触摸事件。核心方法dispatchTransformedTouchEvent()
//下面我们来看dispatchTransformedTouchEvent()方法的实现
--------省略很多代码--------
}
return handled
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case.
// We don't need to perform any transformations
// or filtering.The important part is the action, not the contents.
// ACTION_CANCEL动作是一个特例。我们不需要执行任何转换
// 或过滤。重要的是动作本身,而不是内容。
//MotionEvent.ACTION_CANCEL:表示当前手势已经终止
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {//如果Child View为空,则只分发到ViewGroup中
handled = super.dispatchTouchEvent(event);
} else {//否则分发到Child View
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
// Calculate the number of pointers to deliver.
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
// If for some reason we ended up in an inconsistent state where it looks like we
// might produce a motion event with no pointers in it, then drop the event.
if (newPointerIdBits == 0) {
return false;
}
// If the number of pointers is the same and we don't need to perform any fancy
// irreversible transformations, then we can reuse the motion event for this
// dispatch as long as we are careful to revert any changes we make.
// Otherwise we need to make a copy.
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
- View
通常来说View是没有事件分发的,因为它处在事件分发的最低端,但事实上View也是有事件分发的,只不过你的它的事件分发是用来区分setOnTouchListener、 onTouchEvent、setOnLongClickListener、setOnClickListener
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
* 将触摸屏运动事件向下传递到目标视图或者它本身
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
***省略代码***
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
//ListenerInfo 是一个静态类,里面持有各种监听事件的实现(当然默认是没有实现)
ListenerInfo li = mListenerInfo;
//最先判断OnTouchListener是否为空,不为空执行OnTouchListener.onTouch()
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//然后判断执行onTouchEvent()方法
//最后会在onTouchEvent()中判断是否执行长按事件和点击事件
if (!result && onTouchEvent(event)) {
result = true;
}
}
***省略代码****
return result;
}
public boolean onTouchEvent(MotionEvent event) {
***省略代码***
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP://是否触发点击事件
***省略代码***
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
***省略代码***
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
//只有当我们处于按下状态时才执行单击操作
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
//使用Runnable执行而不是直接调用它,
//这样能允许在单击动作开始之前更新视图的其他视觉状态。
//PerformClick是Runnable的实现类,它执行的是performClickInternal(),
//而performClickInternal()会调用performClick(),
//最终performClick()中会调用OnClickListener.onClick()
/*************************************************
private final class PerformClick implements Runnable {
@Override
public void run() {
performClickInternal();
}
}
private boolean performClickInternal() {
// Must notify autofill manager before performing the click actions to avoid scenarios where
// the app has a click listener that changes the state of views the autofill service might
// be interested on.
notifyAutofillManagerOnClick();
return performClick();
}
public boolean performClick() {
******省略代码********
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
****省略代码*****
return result;
}
************************************************/
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
***省略代码***
break;
case MotionEvent.ACTION_DOWN://是否触发长按事件
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
//重点看checkForLongClick()方法
/*******************************************************
private void checkForLongClick(int delayOffset, float x, float y) {
if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE || (mViewFlags & TOOLTIP) == TOOLTIP) {
mHasPerformedLongPress = false;
//CheckForLongPress也是Runnable的实现类
if (mPendingCheckForLongPress == null) {
mPendingCheckForLongPress = new CheckForLongPress();
}
mPendingCheckForLongPress.setAnchor(x, y);
mPendingCheckForLongPress.rememberWindowAttachCount();
mPendingCheckForLongPress.rememberPressedState();
//通过延迟发送实现长按事件
postDelayed(mPendingCheckForLongPress,
ViewConfiguration.getLongPressTimeout() - delayOffset);
}
}
private final class CheckForLongPress implements Runnable {
private int mOriginalWindowAttachCount;
private float mX;
private float mY;
private boolean mOriginalPressedState;
@Override
public void run() {
if ((mOriginalPressedState == isPressed()) && (mParent != null)
&& mOriginalWindowAttachCount == mWindowAttachCount) {
//最终调用performLongClick(),而它又调用performLongClickInternal()
//最后调用OnLongClickListener.onLongClick()方法
//其实跟点击是一个路子
if (performLongClick(mX, mY)) {
mHasPerformedLongPress = true;
}
}
}
public void setAnchor(float x, float y) {
mX = x;
mY = y;
}
public void rememberWindowAttachCount() {
mOriginalWindowAttachCount = mWindowAttachCount;
}
public void rememberPressedState() {
mOriginalPressedState = isPressed();
}
}
public boolean performLongClick() {
return performLongClickInternal(mLongClickX, mLongClickY);
}
private boolean performLongClickInternal(float x, float y) {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
boolean handled = false;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnLongClickListener != null) {
handled = li.mOnLongClickListener.onLongClick(View.this);
}
******省略代码******
return handled;
}
*********************************************************/
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
if (performButtonActionOnTouchDown(event)) {
break;
}
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
//CheckForTap也是Runnable的实现类,最终也会调用checkForLongClick()
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
//通过延迟发送
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
***省略代码***
break;
case MotionEvent.ACTION_MOVE:
***省略代码***
break;
}
return true;
}
return false;
}
由此我们可以知道View的监听事件的调用顺序为:setOnTouchListener-> onTouchEvent->setOnLongClickListener->setOnClickListener