Learn && Live
虚度年华浮萍于世,勤学善思至死不渝
前言
Hey,欢迎阅读Connor学Android系列,这个系列记录了我的Android原理知识学习、复盘过程,欢迎各位大佬阅读斧正!原创不易,转载请注明出处:http://t.csdn.cn/W5DRa,话不多说我们马上开始!
1.Activity对点击事件的分发过程
点击事件用MotionEvent表示,当一个点击操作发生时,事件最先传递给当前的Activity,由Activity的dispatchTouchEvent派发事件,具体的工作则由Activity内部的Window完成,而Window会将时间进一步传递给DecorView,这个DecorView一般就是当前界面的底层容器(setContentView设置的View的父容器),可以通过Activity.getWindow().getDecorView()获得。
// Activity#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
// (1)
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
在注释(1)处,事件会交给Activity所附属的Window分发事件,如果返回true,则整个事件循环就结束了,返回false则说明所有的子View的onTouchEvent均返回了false,即事件没人处理,此时就会调用Activity的onTouchEvent
Window是一个抽象类,其superDispatchTouchEvent方法也是一个抽象方法。经查源码,PhoneWindow是Window的唯一实现类,实现了superDispatchTouchEvent方法
// PhoneWindow#superDispatchTouchEvent
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
//mDecor是DecorView的实例
return mDecor.superDispatchTouchEvent(event);
}
很明显是一个由Window向DecorView分发事件的过程,那么DecorView究竟是什么呢?
private final class DecorView
extends FrameLayout
implements RootViewSurfaceTaker {
private DecorView mDecor;
@Override
public final View getDecorView() {
if(mDecor == null) {
installDecor();
}
return mDecor;
}
}
可以看出,DecorView继承自FrameLayout,而FrameLayout继承了ViewGroup。因此获取DecorView对象,再调用其父类的dispatchTouchEvent,实际上就是完成了Window向顶级View(ViewGroup)分发事件的过程
结论
当一个点击事件发生时,调用顺序如下
(1)事件最先传到Activity的dispatchTouchEvent()进行事件分发
(2)调用Window类实现类PhoneWindow的superDispatchTouchEvent()
(3)调用DecorView的superDispatchTouchEvent(),明确DecorView继承FrameLayout
(5)最终调用DecorView父类的dispatchTouchEvent(),即ViewGroup的dispatchTouchEvent()
2.顶级View对点击事件的分发过程
// ViewGroup#dispatchTouchEvent
// 发生ACTION_DOWN事件或者已经发生过ACTION_DOWN,并且将mFirstTouchTarget赋值,才进入此区域,主要功能是拦截器
final boolean intercepted;
// (1)
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
// (2)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//默认情况下会进入该方法
if (!disallowIntercept) {
// (3)
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
// 当没有触摸targets,且不是down事件时,开始持续拦截触摸。
intercepted = true;
}
actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null
(1)mFirstTouchTarget用于指向ViewGroup的某一子元素,即当事件由ViewGroup的子元素成功处理时,会被赋值并指向子元素
(2)mFirstTouchTarget != null代表ViewGroup不拦截事件而是继续交由子元素处理
(3)actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null表示当ACTION_MOVE和ACTION_UP时间到来时,同时ViewGroup不拦截事件,则ViewGroup不会再调用onInterceptTouchEvent方法拦截后续事件
disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0
(1)FLAG_DISALLOW_INTERCEPT标记位由requestDisallowInterceptTouchEvent方法来设置,一般由子View设置。一旦设置,ViewGroup将无法拦截除ACTION_DOWN以外的其他点击事件(因为ACTION_DOWN会重置这个标志位,导致子View的设置失效)
(2)disallowIntercept表示是否禁用事件拦截的功能,默认是false,即不禁用
当ViewGroup不拦截事件时,事件会下发给当前ViewGroup的子View或ViewGroup处理
总结
当一个事件序列的ACTION_DOWN时间到来,ViewGroup调用onInterceptTouchEvent方法判断是否拦截
-
若拦截,mFirstTouchTarget不赋值,继续处理,且不会再向子元素分发本次事件序列
-
若不拦截则继续向子元素传递,子元素如果处理了事件,则ViewGroup不会再接收该事件
// (1)
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);
// (1)-1
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
// (1)-2
if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
// (2)
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
// (3)
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 获取TouchDown的时间点
mLastTouchDownTime = ev.getDownTime();
// 获取TouchDown的Index
if (preorderedList != null) {
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
//获取TouchDown的x,y坐标
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//添加TouchTarget,则mFirstTouchTarget != null。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//表示以及分发给NewTouchTarget
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
(1)遍历ViewGroup的所有子元素,依次判断是否能够接收到点击事件,判断条件如下
- 注释(1)-1,子元素是否获取了用户焦点
- 注释(1)-2,子元素是否可见或点击事件的坐标是否落在子元素的区域内
(2)获取当前的TouchTarget(表示的意义与mFirstTouchTarget同理),若不为空,代表当前子元素接收了事件,结束遍历,否则继续
(3)当前View没有接收事件,则会调用dispatchTransformedTouchEvent(实际上就是dispatchTouchEvent)继续向下分发
- 如果dispatchTransformedTouchEvent返回true,代表有子元素接收了事件,更新newTouchTarget(mFirstTouchTarget)
- 如果dispatchTransformedTouchEvent返回false,则由子元素继续完成后续的分发操作
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);
}
(4)如果遍历了所有子元素后事件都没有被合适地处理(ViewGroup没有子元素或子元素未处理事件),ViewGroup会自己处理事件
3.View对点击事件的处理过程
public boolean dispatchTouchEvent(MotionEvent event) {
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
mOnTouchListener != null
public void setOnTouchListener(OnTouchListener l) {
mOnTouchListener = l;
}
mOnTouchListener是在View类下setOnTouchListener方法里赋值的
即只要我们给控件设置了OnTouchListener,mOnTouchListener就一定被赋值(不为空)
(mViewFlags & ENABLED_MASK) == ENABLED
该条件是判断当前点击的控件是否enable。由于很多View默认是enable的,因此该条件恒定为true
mOnTouchListener.onTouch(this, event))
- 如果在onTouch方法返回true,就会让上述三个条件全部成立,从而整个方法直接返回true。
- 如果在onTouch方法里返回false,就会去执行onTouchEvent(event)方法。
下面看onTouchEvent(event)方法
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
// (1)
if ((viewFlags & ENABLED_MASK) == DISABLED) {
return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
// (2)
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
// (3)
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)) {
// (4)
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:
...
break;
case MotionEvent.ACTION_CANCEL:
...
break;
case MotionEvent.ACTION_MOVE:
...
break;
}
return true;
}
return false;
}
(1)当View处于不可用状态(disable),依然会消耗事件。只要它的clickable或longClickable有一个为true,onTouchEvent就返回true
(2)如果View设置有代理,则还会执行TouchDelegate的onTouchEvent方法,这里暂不展开介绍
(3)只要View的clickable或longClickable有一个为true,就会消耗事件,分别对DOWN、MOVE、UP、CANCEL事件做具体的处理
(4)在处理ACTION_UP事件时,会触发performClick方法,如果View设置了OnClickListener,则会调用onClick方法
public boolean performClick() {
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
// (4)
if (mOnClickListener != null) {
playSoundEffect(SoundEffectConstants.CLICK);
mOnClickListener.onClick(this);
return true;
}
return false;
}
一点补充
(1)View的LONG_CLICKABLE属性默认为false,而CLICKABLE属性则与具体的View有关,可点击的为true,不可点击的为false
(2)通过setLongClickable、setClickable可分别改变上述两个属性
(3)setOnClickListener会自动将View的CLICKABLE设为true,setOnLongClickListener会自动将View的LONG_CLICKABLE设为true
public void setOnClickListener(OnClickListener l) {
if (!isClickable()) {
setClickable(true);
}
mOnClickListener = l;
}
public void setOnLongClickListener(OnLongClickListener l) {
if(!isLongClickable()) {
setLongClickable(true);
}
getListenerInfo().mOnLongClickListener = l;
}