一、事件分发概述
1.1、什么是事件分发
点击事件MotionEvent
从Activity
传递到ViewGroup
再分发到View
的一整个过程
2.1 MotionEvent
MotionEvent的常用事件类型如下
其中ACTION_POINTER_DOWN和ACTION_POINTER_UP无法通过MotionEvent.getAction()获取,需要通过MotionEvent.getActionMasked()
获取事件类型,在处理多点触控的场景需要使用到
pointer: 在多点触控中引入了一个pointer的概念,MotionEvent
中引入了Pointer
的概念,一个pointer就代表一个触摸点,每个pointer都有自己的事件类型,也有自己的横轴坐标值。一个MotionEvent对象中可能会存储多个pointer的相关信息,每个pointer都会有一个自己的PointerId和PointerIndex。pointer的id在整个事件流中是不会发生变化的,但是index会发生变化
PointerId: 每根手指从按下、移动到离开屏幕,每个手指都会拥有一个固定PointerId.PointerId
的值,一般用它来区分是哪根手指
PointerIndex: 每根手指从按下、移动到离开屏幕,每根手指在每一个事件的Index可能是不固定的,因为受到其它手指的影响
类似MeasureSpec
一样,Google工程师使用了一个32位(0x00000000)的int类型表示PointerIndex和事件类型,最低8位(0x000000ff)表示事件类型,再往前的8位(0x0000ff00)表示事件编号
1.2、流程图
二、源码分析
2.1、Activity事件分发
从Activity的dispatchTouchEvent()方法开始事件序列
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//down事件发生时触发,默认是空实现
onUserInteraction();
}
//实际调用的是ViewGroup.dispathcTouchEvent()
if (getWindow().superDispatchTouchEvent(ev)) {
//如果ViewGroup.dispatchTouchEvent()返回true,表示事件被消费,不会执行Activity.onTuchEvent()
return true;
}
//如果ViewGroup.dispatchTouchEvent()返回true,表示不消费事件,执行Activity.onTouchEvent()
return onTouchEvent(ev);
}
onUserInteraction(): 默认是空实现的,平常使用场景不多但在特定场景还是有用的, onUserInteraction方法的作用是当触屏点击按home,back,menu键等都会触发此方法。下拉statubar、旋转屏幕、锁屏不会触发此方法,所以可以用在屏保应用上
继续看为什么说getWindow().superDispatchTouchEvent(ev)
实际调用的是ViewGroup.dispatchTouchEvent(ev)
呢,getWindow()
获取Activity
的Window
类型的mWindow
成员变量,Window是一个抽象类,它有唯一实现类PhoneWindow
,所以getWindow().superDispatchTouchEvent(ev)
实际调用的是PhoneWindow.superDispatchTouchEvent(ev)
public class PhoneWindow extends Window implements MenuBuilder.Callback {
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
...
}
进而调用DecorView的superDispatchTouchEvent(MotionEvent event)
方法,而DecorView继承自FrameLayout,而FrameLayout继承ViewGroup,所以当执行 mDecor.superDispatchTouchEvent(event)
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
...
}
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
@Override
public boolean dispatchTouchEvent(MotionEvent event) {
...
}
...
}
如上Activity->mWindow->mDecorView->ViewGrop,完成了事件从Activity到ViewGroup的分发
2.2、ViewGroup事件分发
分析ViewGroup.dispatchTouchEvent主要代码
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
//如果空间没有被其他控件遮挡,且控件不是隐藏的,这里默认true
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
if (actionMasked == MotionEvent.ACTION_DOWN) {
//这两个方法的作用是在down时把mFirstTouchTarget设置为null
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 判断是否拦截
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOW || mFirstTouchTarget != null) {
//当子View调用了requestDisallowInterceptTouchEvent(true)时disallowIntercept=true
//默认是false的,在使用内部拦截法处理滑动冲突时会用到这个方法
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//当前ViewGroup是否拦截时间,onInterceptTouchEvent返回true时拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
...
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
...
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//遍历子View
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
...
//如果当前子View无法点击则continue到下一个
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//调用子View的dispatchTouchEvent方法
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//已有子view处理事件,newTouchTarget赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
//子View的dispatchTouchEvent方法返回true,这里break了,就不会继续遍历剩
//下的自view,事件已经传递到子View
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
//如果newTouchTarget已赋值则
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
if (mFirstTouchTarget == null) {
// 没有子View消费事件,这里最终会调用当前ViewGroup的onTouchEvent方法
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
...
}
...
return handled;
}
总体的流程是,如果子view没有调用requestDisallowInterceptTouchEvent(true)
和onInterceptTouchEvent()
不拦截,则遍历子view,根据触摸坐标确定子view的dispatchTouchEvent()
是否拦截,如果拦截就交给到子View处理,如果子View不拦截则交给当前ViewGroup的onTouchEvent()
方法
dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign): 通过这个方法完成了从ViewGroup
到View
的事件分发
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.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
//传递到子view的dispatchTouchEvent(event)
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
...
return handled;
}
2.3、View事件分发
public boolean dispatchTouchEvent(MotionEvent event) {
...
if (onFilterTouchEventForSecurity(event)) {
//控件是enable的
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//这个ListenerInfo包含了设置的一些点击 长按 touch的监听
ListenerInfo li = mListenerInfo;
//如果设置了setOnTouchListener并且它的匿名内部类的onTouch(v,event)返回true则直接返回true事件被消费
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//如果没有设置setOnTouchListener或者onTouch(v,event)返回false,则会执行onTouchEvent方法
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
从上面可以知道设置了setOnTouchListener并且它的匿名内部类的onTouch(v,event)
返回true则直接返回true消费了事件,后续的onTouchEvent
方法不会被执行
如果没有设置setOnTouchListener
或者onTouch(v,event)
返回false,则会执行onTouchEvent方法
再来看一下onTouchEvent
方法和点击事件onClick
方法的关系
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.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
}
mIgnoreNextUpEvent = false;
break;
}
return true;
}
return false;
}
从这里可以看出onClick
事件是通过onTouchEvent
中ACTION_UP
中调用performClickInternal()
执行的
所以这三个事件的执行顺序为setOnTouchEventListener.onTouch
> onTouchEvent
> setOnclickListener.onClick
作者:只玩鲁班
转载来源于:https://juejin.cn/post/7070397183048024071
如有侵权,请联系删除!
网友评论