一个点击或触摸事件会被内部封装成MotionEvent对象。而事件分发就是将MotionEvent往子View传递。有View的地方就有Window,View必须依附于Window进行展示。我们通过setContentView()设置的布局,会被添加到DecorView中,DecorView会被添加到Window,而Window则被加到Activity中,这里用一张图展示它们的层次关系。
事件的分发会经历3个方法,往下递归调用,分别是dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent方法。
从子View的角度来说,dispatchTouchEvent可以理解为接收到事件的意思,也就是当父View要把事件发给我时,会调用dispatchTouchEvent来告知我接收这个事件,所以Activity、ViewGroup和View都有这个方法。
onInterceptTouchEvent是把事件拦截了的意思,Activity是下发的源头,不能还没发出去就把事件拦截掉,而View不是容器,是没有子View的,只有消费和不消费事件一说,没有必要拦截,所以,Activity和View都不会有onInterceptTouchEvent这个方法。
最后一个onTouchEvent是用来决定是否要消费事件的,当然消费事件不止它一个,它的优先级也不是最高的,还有设置触摸监听的setOnTouchListener和点击事件onClick方法。
这里假设一个最简单的场景,ContentView里有一个ViewGroup,ViewGroup里放了一个View。下面通过源码一步步分析,但手指按下时,Activity 会首先收到一个MotionEvent事件,它为ACTION_DOWN类型的。
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback {
//事件分发源头
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//通过Window下发
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//自己处理
return onTouchEvent(ev);
}
//空实现
public void onUserInteraction() {
}
}
Window是Activity的下一层,而Activity接到事件后,并没做什么处理,而是将事件交给Window去做分发。如果superDispatchTouchEvent返回了true,即说明这个事件被某个控件消费了,流程结束。如果返回false,说明这个事件没有控件消费,则调用自己的onTouchEvent把事件消费掉。
接下来看Window如何将事件传递给ViewGroup。
public abstract class Window {
public abstract boolean superDispatchTouchEvent(MotionEvent event);
}
Window 是一个抽象类,superDispatchTouchEvent也是一个抽象方法,它的唯一实现类为PhoneWindow。
public class PhoneWindow extends Window implements MenuBuilder.Callback {
private DecorView mDecor;
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
}
PhoneWindow也什么都没做,将事件交给Window去做分发。
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
}
前面的调用方法都是superDispatchTouchEvent,而不是dispatchTouchEvent,而这里我们第一次看到dispatchTouchEvent的调用,也就是说,从此处,事件开始向下一层层分发给子View了。
而DecorView 继承自FrameLayout ,我们知道FrameLayout 是一个容器,即继承了ViewGroup,而且FrameLayout 也没有重写dispatchTouchEvent方法。
public class FrameLayout extends ViewGroup {
}
因此,事件将通过ViewGroup来处理。上面提到过,此时我们可以认为,DecorView是第一个接到事件的ViewGroup。由于dispatchTouchEvent方法比较长,我们一段段分析。
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
protected int mGroupFlags;
private TouchTarget mFirstTouchTarget;
//DecorView开始事件分发
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
//事件是否被处理了
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//每次ACTION_DOWN都重新开始分发的流程,所以清理掉之前的事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
//清除之前的所有事件
cancelAndClearTouchTargets(ev);
//重置
resetTouchState();
}
//检查是否要拦截
final boolean intercepted;
//当不是Down事件时:
// 如果子View消费了事件,mFirstTouchTarget不为null,
// mFirstTouchTarget为null,说明子View都没有消费,直接跳else执行拦截
if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
//是否拦截
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//不拦截进入判断
if (!disallowIntercept) {
//如果重写onInterceptTouchEvent返回true,此处intercepted = true进行拦截
intercepted = onInterceptTouchEvent(ev);//默认为false
//恢复事件防止其改变
ev.setAction(action);
} else {
//不拦截
intercepted = false;
}
} else {
//子控件没有消费DOWN事件
intercepted = true;
}
这里有几个关键的变量,intercepted用来控制事件是否要继续往下分发,初始值为false,即不拦截。正常情况下,当按下手指时,第一个事件会为ACTION_DOWN,即actionMasked是ACTION_DOWN。mFirstTouchTarget ,见名知意,它表示第一个事件由谁消费,但不包括自己,意思是,如果有子控件消费了down事件,那么mFirstTouchTarget 就会指向那个对象,不为null,如果没有子控件消费down事件,则为null。
那么当用户按下手指饼滑动时,第二个传递过来的事件则为ACTION_MOVE,actionMasked == MotionEvent.ACTION_DOWN不成立,如果刚才的ACTION_DOWN事件子控件没有消费,mFirstTouchTarget !=null也不成立,将进入else判断,intercepted 置为true。
也就是说,子控件一开始没有消费ACTION_DOWN事件的话,那则往后的事件都默认拦截,不会往下发了。同时也不会再执行到onInterceptTouchEvent这个方法。
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
//重置为0,即false
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
//请求父控件不拦截事件
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
//如果子控件请求的和当前状态相同,无需往下执行
if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
return;
}
//是否拦截
if (disallowIntercept) {
mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
} else {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
//有父类继续告知
if (mParent != null) {
mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
}
}
FLAG_DISALLOW_INTERCEPT是一个标志位,它可以通过requestDisallowInterceptTouchEvent来设置是否进行拦截方法的调用,它一般用于子View来请求父控件拦截或不拦截事件,但只能作用于非ACTION_DOWN的情况下。因为每次ACTION_DOWN都是一轮分发的重新开始,也就是说之前的事件都废弃了。resetTouchState会重置状态,包括FLAG_DISALLOW_INTERCEPT这个标志位。
所以,接到ACTION_DOWN事件时,总会执行到onInterceptTouchEvent方法。
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
这个方法很简单,如果子类不重写的话默认返回false,不拦截。
//检查事件是否取消
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//如果有必要,为DOWN事件检查所有的目标对象
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;//链表节点
boolean alreadyDispatchedToNewTouchTarget = false;
//事件没有取消,同时没有拦截,则往下分发
if (!canceled && !intercepted) {
//如果事件为起始事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex();
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
//newTouchTarget为null,并且有子View
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final ArrayList preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//倒序寻找是否有子View处理此事件
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)? children[childIndex] : preorderedList.get(childIndex);
//如果子View有动画在执行,或者没有落在这个View的范围内,就继续找下一个子View
if (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
//将接收事件的子View赋值给newTouchTarget
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//1,如果子View是ViewGroup,会递归调用此dispatchTouchEvent往下分发,如果返回某个View返回true,则说明被消费了,否则没有消费,继续下发,直到最后一个是View,即2的情况
//2,如果子View不包含子View了,dispatchTransformedTouchEvent会调用View的dispatchTouchEvent方法,进而调用onTouchEvent如果返回true,说明被消费了,否则没有消费
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
......
//是否在执行动画
private static boolean canViewReceivePointerEvents(View child) {
return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null;
}
此时如果事件没有被取消,则会遍历所有的子控件,看哪一个能接收这个事件,条件有两个,即控件是否有动画在执行,和是否落在触摸的区域。getTouchTarget这个方法用于查找这个View是否在当前的ViewGroup中。
private TouchTarget getTouchTarget(View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}
当View被添加到ViewGroup中时,会通过链表结构,将View封装成一个TouchTarget的链表节点,这里是遍历链表,如果找到了,返回这个TouchTarget节点,并跳出上面的遍历循环。
此时,流程执行到dispatchTransformedTouchEvent方法,它是处理事件继续向下分发的关键方法。
//传递到子View的事件
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
//是否消费了事件
final boolean handled;
final int oldAction = event.getAction();
//取消事件
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
//没有子View消费事件,看自己消不消费
if (child == null) {
//直接调用父类的dispatchTouchEvent
handled = super.dispatchTouchEvent(event);
} else {
//如果有子View,将cancle事件传递到子View中
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
//计算即将被传递的点的数量
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
//如果事件没有相应的点,那么丢弃该事件
if (newPointerIdBits == 0) {
return false;
}
//保存坐标转换后的MotionEvent
final MotionEvent transformedEvent;
//如果事件点的数量一致
if (newPointerIdBits == oldPointerIdBits) {
//子元素为空,或有一个单位矩阵
if (child == null || child.hasIdentityMatrix()) {
//空的情况
if (child == null) {
//调用父类的dispatchTouchEvent
handled = super.dispatchTouchEvent(event);
} else {
//获取xy方向的偏移量(使用scrollTo或scrollBy滚动)
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
//将MotionEvent进行坐标变换
event.offsetLocation(offsetX, offsetY);
//变换后的MotionEvent传给子View
handled = child.dispatchTouchEvent(event);
//复位MotionEvent以便之后再次使用
event.offsetLocation(-offsetX, -offsetY);
}
//返回是否消费
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
if (child == null) {
//如果没有包含子view了,则使用父类View的dispatchTouchEvent
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());
}
//有子View,则调用子View的dispatchTouchEvent往下分发
handled = child.dispatchTouchEvent(transformedEvent);
}
transformedEvent.recycle();
return handled;
}
这里先对父类/子类和父View/子View名词做下强调,前一组指的是继承关系,后一组是包含关系。
- 如果上面遍历找到接收的子View不为null,则调用子View的dispatchTouchEvent方法,将事件传递给子View。
- 如果子View为null,事件不再往下传递,调用父类,即View的dispatchTouchEvent方法。
我们知道,控件要么是ViewGroup,要么是View。这意味着,无论是ViewGroup还是View,如果进入了View类的dispatchTouchEvent方法,也就开始是否消费事件的流程了。
我们回头再分析View的dispatchTouchEvent方法。先假设handled最后返回了true,则dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)判断满足。
......
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//子View接收到事件的时间戳
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
//找到在列表中的角标
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消费了,把消费了事件的View对应的消费节点,赋值给newTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
if (preorderedList != null) preorderedList.clear();
}
//如果此时触发没有找到接收的View,将最近一次的目标引用,作为当前事件的的目标引用
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
}
接收ACTION_DOWN事件的控件找到了,于是,通过addTouchTarget方法,一开始定义的mFirstTouchTarget变量便被赋值,指向对应的控件,并终止对子View的遍历。如果上面的handled返回了false,而且还有子View的话,将继续遍历寻找。
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
//将目标控件构建成一个TouchTarget 节点
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
//赋值给mFirstTouchTarget
mFirstTouchTarget = target;
return target;
}
如果找寻到最后,mFirstTouchTarget 仍旧为null,回看到我们一开始的分析,intercepted将置为ture,同一轮中后面所有的事件都被默认拦截了。也不会再遍历包含的子View了。
......
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
//没有子View消费此事件
if (mFirstTouchTarget == null) {
//child为null,会执行View的dispatchTouchEvent
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
} else {
//往后的事件都分发给mFirstTouchTarget
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;
//mFirstTouchTarget 消费事件
if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
if (canceled || actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
这里的代码非常关键,无论mFirstTouchTarget 是不是null,后面的事件都不会再遍历找寻子View。
当mFirstTouchTarget 不为null时,后面的事件都直接交给mFirstTouchTarget这个控件。
当mFirstTouchTarget 为null时,事件将由自己做消费判断。
出现第二种情形包含3种可能:
- ViewGroup没有子View。
- ViewGroup所有的子View都没有落在触摸的范围。
- 子View的dispatchTouchEvent返回false,一般在onTouchEvent中返回。
相同的是,最终都会调用dispatchTransformedTouchEvent方法,但传入的参数不同,最主要是第3个参数,一个传入null,表示自己消费,一个传入target.child,目标控件消费。但最终都进入View的dispatchTouchEvent方法。
public boolean dispatchTouchEvent(MotionEvent event) {
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
//ListenerInfo封装了手势监听,触摸监听等监听接口
ListenerInfo li = mListenerInfo;
//设置的OnTouchListener
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//如果上面result不为true,执行onTouchEvent
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
stopNestedScroll();
}
return result;
}
如源码所示,OnTouchListener优先级大于onTouchEvent,而且随后的分析将看到,onClick是在onTouchEvent中被调用的,所以,如果setOnTouchListener的onTouch返回true,那么onTouchEvent方法不会执行,setOnClickListener的onClick回调也不会执行。
我们依旧假设OnTouchListener返回false,事件进入onTouchEvent方法。
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
//不可用状态也消费
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
//只要可点击就返回true
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
//设置了代理
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//注意if作用域结束后返回true
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
switch (event.getAction()) {
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
setPressed(true, x, y);
}
if (!mHasPerformedLongPress) {//长按则不响应点击事件
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//点击事件进入队列执行
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
//按下
case MotionEvent.ACTION_DOWN:
//是否长按
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
boolean isInScrollingContainer = isInScrollingContainer();
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
//状态改为按下,比如button这时会变背景,需要刷新ui
setPressed(true, x, y);
checkForLongClick(0);//检查并保存是否长按状态
}
break;
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
case MotionEvent.ACTION_MOVE:
drawableHotspotChanged(x, y);
//是否超出View的范围
if (!pointInView(x, y, mTouchSlop)) {
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
//移除长按的监听
removeLongPressCallback();
//不是按下状态
setPressed(false);
}
}
break;
}
return true;
}
return false;
}
我们可以得出这样的结论:View的enable属性并不影响事件的消费,只要CLICKABLE和LONG_CLICKABLE有一个为true,onTouchEvent就会返回ture,将事件消费。
ACTION_UP事件发生时,如果外部进行setClickListener,将触发performClick(),回调onClick方法。
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li != null && li.mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
//回调onClick方法
li.mOnClickListener.onClick(this);
result = true;
} else {
result = false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
View的LONG_CLICKABLE默认为false,不可点击的View,CLICKABLE默认为false,比如TextView,可点击的View,CLICKABLE则为true,比如Button。但是,我们知道View有监听点击的方法,setOnLongClickListener会将LONG_CLICKABLE置为true,setOnClickListener将CLICKABLE置为true。
public void setOnLongClickListener(OnLongClickListener l) {
if (!isLongClickable()) {
setLongClickable(true);
}
getListenerInfo().mOnLongClickListener = l;
}
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
getListenerInfo().mOnClickListener = l;
}
到这里,整个事件分发的流程分析完毕。事件回调优先级顺序也清晰了:
onTouch > onTouchEvent > onClick