Android事件传递(一):Activity、View、ViewGroup及dispatchtouchEvent、onTouchEvent梳理
Android事件传递(三):事件动作 UP 在Activity、View、ViewGroup传递
下面源码基于Android11 API30
接上一篇文章,我们从Activity开始分析ACTION_DOWN动作的传递:
1 Activity#dispatchTouchEvent
public class Activity extends ContextThemeWrapper
implements Window.Callback,...... {
......省略其它代码......
private Window mWindow;
public Window getWindow() {
return mWindow;
}
@UnsupportedAppUsage
final void attach(Context context,......) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
👉 mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setCallback(this);
......省略......
}
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
✍空实现,你可以重写此方法实现自己的业务要求,但是只是DOWN动作会调用,
其它后续动作都不会执行该方法
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
✍getWindow()得到的就是PhoneWindow,所以去看它的superDispatchTouchEvent(ev)方法
return true;
}
return onTouchEvent(ev);
}
public void onUserInteraction() {
}
}
👇 PhoneWindow.java
public class PhoneWindow extends Window implements MenuBuilder.Callback {
......
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
✍mDecor即DecorView
return mDecor.superDispatchTouchEvent(event);
}
}
👇 DecorView.java
public class DecorView extends FrameLayoutimplements RootViewSurfaceTaker, ......{
......
public boolean superDispatchTouchEvent(MotionEvent event) {
✍ DecorView extends FrameLayout,间接继承ViewGroup
所以接着去看ViewGroup的dispatchTouchEvent
return super.dispatchTouchEvent(event);
}
}
👇 ViewGroup.java
@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
......省略其它代码......
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
......省略其它代码......
//✍默认值false
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
//✍当前事件为ACTION_DOWN,清除之前所有状态为新一轮事件做准备
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//✍判断事件是否被拦截,即onInterceptTouchEvent方法的返回值
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//✍disallowIntercept 是否允许拦截事件,通过requestDisallowInterceptTouchEvent方法设置,该方法是ViewParent接口中的方法,ViewGroup实现了该方法,
//如果是Button等不是继承自ViewGroup的控件要使用getParent().requestDisallowInterceptTouchEvent(boolean)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//✍onInterceptTouchEvent(ev)就是ViewGroup特有的方法,如果你想在某布局中拦截事件,重写该方法用来拦截事件
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//✍如果禁止父布局拦截事件,直接置为false
intercepted = false;
}
} else {
//✍如果当前事件不是ACTION_DOWN,而是后续的MOVE或者UP。比如UP事件的传递就是在这里终止,会在下一篇文章详细说
intercepted = true;
}
//✍判断当前事件是否已被取消
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
boolean alreadyDispatchedToNewTouchTarget = false;
//✍没有被拦截且没有被取消
if (!canceled && !intercepted) {
//✍ACTION_DOWN动作才会遍历当前布局所有子控件去寻找有无接收事件的
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x =
isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
final float y =
isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
// ✍遍历当前布局中所有子控件,这不是嵌套布局循环是当前布局的子控件,它们是并列的。
//从childrenCount - 1倒着遍历,是因为子控件如果有重叠一定是后面的盖在前面控件的上面,我们的事件要先判断上面的控件要不要处理事件,如果不处理在判断下面的。
for (int i = childrenCount - 1; i >= 0; i--) {
//✍自定义控件通过重写getChildDrawingOrder方法,可以改变子控件的加载顺序,所以这里获取其在父布局中真正的位置。
//例如自定义RecyclerView就可以重写该方法修改item的加载顺序
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
//✍这里是判断当前child是否能接收到事件,或者事件的坐标是否在其范围内,就是你点没点到当前child上,你都没有点到它上面就把事件给它肯定是不合理的。
//当前child无法接收事件或者你点的坐标不在它的范围内,就跳过它,继续判断下一个child。
continue;
}
//✍判断child是否已在mFirstTouchTarget链表中
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
//✍如果在mFirstTouchTarget链表中找到child就停止寻找当前事件接收控件并把当前事件的idBitsToAssign添加到其pointerIdBits中
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
✍ 🔺重头戏来喽,到这里我们已经找到我们点击上的一个child,下面就判断这个child是不是接收处理事件。这里我给它起了个名字:嵌套循环起源。后面会用到这个名字!!!
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
......
}
if (preorderedList != null) preorderedList.clear();
}
.......
}
}
......后面部分放在后面分析..........
return handled;
}
}
👇看一下 dispatchTransformedTouchEvent(MotionEvent , boolean ,View , int )方法
⚠️🔺🔺这里分为三种情况
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
**************************** 情况1 事件被取消 ******************************************
//✍先保存当前event的action到oldAction中
final int oldAction = event.getAction();
//✍如果已取消
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
//✍把当前event的action设置为ACTION_CANCEL
//重要的是将ACTION_CANCEL这一动作传递下去,而不需要其他另外的变换操作。
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
//这里child为啥有可能为null呢?这里正常父布局有子布局,子布局不可能为null。而是因为
//调用dispatchTransformedTouchEvent有多处非一处,有可能传的就是null.
//所以此处是否为null是区分是否自己处理还是继续纵向传递
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
********************************** 情况2 最常用的 详细分析 *******************************
//✍获取所有触摸点,放在一个int中高8位索引低8位动作类型;因为我们可能会多指操作而不是一个手指
final int oldPointerIdBits = event.getPointerIdBits();
//✍ &(与操作)是都是1才为1。我们一般都是单指操作所以oldPointerIdBits和desiredPointerIdBits相同与操作以后还是不变即newPointerIdBits = oldPointerIdBits;
//如果oldPointerIdBits 存有多个触摸点再和当前一个动作的desiredPointerIdBits进行&操作得到的newPointerIdBits肯定和oldPointerIdBits不相等。
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
//✍由于某些异常导致接收触摸点找不到,就直接抛弃当前事件
if (newPointerIdBits == 0) {
return false;
}
final MotionEvent transformedEvent;
//✍一般情况下我们都是一根手指操作屏幕,新旧触摸点都是一个所以相等
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
//✍没有child,事件交给自己处理,调用父类View的dispatchTouchEvent,判断是否消费此事件动作
handled = super.dispatchTouchEvent(event);
} else {
//✍这里进行坐标转换,上面CANCEL的情况下就只是传递action,并不需要进行转换操作
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
✍child不为null,分两种(最后面有另外分析):
🔺1.如果child是LinearLayout等直接继承ViewGrou就会嵌套调用dispatchTouchEvent继续检查其内是否有child,这样就把布局层层遍历直到最里层,这里是纵向往布局内层循环的关键!🔺
2.如果child是Button等继承自View那就是调用View的dispatchTouchEvent判断当前child是否接收处理当前事件。会根据是否接收事件开始逐层向外返回了。
//PS:这里要注意比如child是你自己定义了一个LinearLayout重写了dispatchTouchEvent
//这里就会执行你自己重写的dispatchTouchEvent除非你使用默认返回值super.dispatchTouchEvent(ev),
//否则无论你返回true还是false,这个事件就不会继续往内部传递了。
//因为执行的是你自己重写的dispatchTouchEvent而不是ViewGroup的dispatchTouchEvent你自己的dispatchTouchEvent方法里可没有for循环遍历child来传递事件
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
//✍新旧触摸点不相同的情况下进行拆分新构成一个MotionEvent transformedEvent
transformedEvent = event.split(newPointerIdBits);
}
********************************** 情况3 ********************************************
//✍ 这个怎么又来了一个这样的判断,前面都有两个了,这里这个有什么用?
//还记得上面newPointerIdBits != oldPointerIdBits情况下拆分出来一个MotionEvent transformedEvent 上面只是拆分事件还没分发,在这里进行分发。
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;
}
🔺:我们最终目的是找到接收事件的控件,那首先是找到布局的最里层控件
if (child == null) {
✍ 1 里层布局容器没有child,事件交给自己处理,调用父类View的dispatchTouchEvent,判断是否消费此事件动作
handled = super.dispatchTouchEvent(event);
} else {
✍ 2 到这里如果child是容器比如Linearlayout会去找它还有没有子布局,如果没有
就是上面child==null; 如果有就继续找直到最里层是像Button等View的子类,
此时child.dispatchTouchEvent就是View.dispatchTouchEvent
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
在找到最里层控件的时候,都是调用View的dispatchTouchEvent方法判断控件是否接收处理事件动作
接下来就看View.dispatchTouchEvent是如何判断控件是否能接收事件动作的。
PS:自定义dispatchTouchEvent上面注释也说了,这里就先不考虑,先走默认流程。
👇那就看一下 View的dispatchTouchEvent(MotionEvent)方法
@UiThread
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
public boolean dispatchTouchEvent(MotionEvent event) {
//✍初始化result默认值
boolean result = false;
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
//✍防止程序恶意遮盖,误导用户
if (onFilterTouchEventForSecurity(event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//✍要到这里满足四个条件:
1 li即mListenerInfo不为null;
2 li.mOnTouchListener不为null即我们自己setOnTouchListener()
3 (mViewFlags & ENABLED_MASK) == ENABLED 控件可操作
4 我们在setOnTouchListener时OnTouchListener中重写的onTouch方法返回true
PS:点进 setOnTouchListener(onTouchListener)方法就是li.mOnTouchListener = onTouchListener
result = true;
}
if (!result && onTouchEvent(event)) {
//✍如果没有满足上一条件result还是false,会调用onTouchEvent(event)分两种情况
// 1 我们重写了onTouchEvent,会根据我们重写的return值来判断
// 2 我们没有重写onTouchEvent,就会调用View默认的onTouchEvent方法(看下面)
result = true;
}
}
return result;
}
//✍Viewm默认onTouchEvent方法
public boolean onTouchEvent(MotionEvent event) {
//✍事件坐标标志,动作等赋值
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
//✍判断是否可点击
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
//✍是否设置委托,一般是不改变布局通过委托扩大点击范围,可去搜索看一下,这里就不说了。
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
//✍抬起动作,标志一个事件的结束。像最常用的点击,就是在UP这个事件结束时中回调而不是DOWN按下时就会判断
case MotionEvent.ACTION_UP:
//✍再判断一次是否可点击,若否直接break
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
//✍没有进行是否长按判断且up动作不可被忽略
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
//✍对于onClickListener的回调是通过post(mPerformClick)而不是直接执行performClickInternal()
//这样可以确保在点击操作执行前视图效果执行完毕,我们就去看看post(mPerformClick)是啥
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
}
break;
//✍按下操作里面会开启是点击还是长按的检测
case MotionEvent.ACTION_DOWN:
//✍是否判断长按默认是false没有判断,只有满足一定条件才会判断是否长按
mHasPerformedLongPress = false;
if (isInScrollingContainer) {
......
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
//✍普通点击事件走这里判断是是否是长按,会执行一个CheckForLongPress其实现了Runnable
checkForLongClick(
ViewConfiguration.getLongPressTimeout(),
x,
y,
TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__LONG_PRESS);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
......
break;
}
return true;
}
return false;
}
}
回调点击监听的 post(mPerformClick) 中先看 mPerformClick = new PerformClick();
private final class PerformClick implements Runnable {
@Override
public void run() {
recordGestureClassification(TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__SINGLE_TAP);
performClickInternal();
}
}
private boolean performClickInternal() {
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);
✍li = mListenerInfo有没有感到很熟悉,设置TouchListener就是用到这个
同样,我们setOnClickListener也是直接赋值给li.mOnClickListener,如果我们
设置了OnClickListener就会在这里被回调
result = true;
} else {
✍如果我们没有设置就走这里 置为false ,最后return出去
result = false;
}
return result;
}
再看View的post( )方法是什么样的
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
✍一般情况下attachInfo 不为null,通过其handler把mPerformClick发送执行
我们的onClickListener回调也就被调用了。
return attachInfo.mHandler.post(action);
}
// Postpone the runnable until we know on which thread it needs to run.
// Assume that the runnable will be successfully placed after attach.
getRunQueue().post(action);
return true;
}
到这里一个事件,层层传递找到接收处理事件的控件并消费掉事件就结束了。通过源码我们也可以看到是先判断OnTouch 再判断OnTouchEvent 最后判断 OnClickListener,这就是这三者的执行先后顺序。
但是,这一次的事件还没有完,我们找到了消费事件的控件接下来还要干啥?我们要开始往回返了。
先回到dispatchTransformedTouchEvent 这里
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
......
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
✍ 要么是这里把返回值给handled,我们让handled=true,有控件接收事件
handled = super.dispatchTouchEvent(event);
} else {
✍ 要么是这里把返回值给handled,我们让handled=true,有控件接收事件
handled = child.dispatchTouchEvent(event);
}
✍ 返回handled的值
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
......
return handled;
}
接着就回到 🔺嵌套循环起源 🔺即 ViewGroup的dispatchTransformedTouchEvent方法那里。
@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
......省略其它代码......
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
......省略其它代码......
if (!canceled && !intercepted) {
for (int i = childrenCount - 1; i >= 0; i--) {
✍ 🔺我们是从这里出去的,现在回到这里: 🔺嵌套循环起源 🔺
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
✍ 进入这个判断条件说明有控件接收处理事件
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();
✍这里会把一个链表TouchTarget对象赋值给mFirstTouchTarget
🔺 addTouchTarget 这个方法非常重要,后面有分析🔺
newTouchTarget = addTouchTarget(child, idBitsToAssign);
✍开关置为true
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}//for (int i = childrenCount - 1; i >= 0; i--) end
if (newTouchTarget == null && mFirstTouchTarget != null) {
✍当右手指按在屏幕上某个控件位抬起,另一手指按在另一控件但是该控件未接收事件,就会把第二根手指的事件绑定到前一个最新的控件上
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}//if (!canceled && !intercepted) end
if (mFirstTouchTarget == null) {
✍1 当没有找到接收处理事件的控件调用dispatchTransformedTouchEvent传过去的child参数值为null,前面这个方法里我们也分析了,这种情况下就是判断自身是否处理事件,
如果自己也不处理就handled = false,又回到前一层父布局的 循环起源 那里,然后再到父布局的ViewGroup又走到这里,继续判断,这样就从内到外判断是否有控件消费事件。
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
✍进入到这里就说明已找到接收事件的控件
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
✍在DOWN事件且找到接收控件alreadyDispatchedToNewTouchTarget被置为true而且target == newTouchTarget,一般到到这里 handled = true 就到最后面return了。
handled = true;
} else {
.... 这里先省略下篇文章UP动作仔细分析 .....
}
predecessor = target;
target = next;
}
}
// Update list of touch targets for pointer up or cancel, if needed.
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;
}
}
OK,到这里dispatchTouchEvent终于终于走完了,别高兴太早!这只是一层布局,还有当前布局的父布局在 🔺嵌套循环起源 🔺那里等着你给他回话要不要接受处理事件!!!
这样我们又回到父布局的 🔺嵌套循环起源 🔺 再回到前面再找父布局,再回到...再找....直到根布局。
如果子布局有控件接收事件,dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)就为true,进入if判断;这样从这个接收事件的控件到它的父布局,再父布局都是true;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
✍都是true就走这里
handled = true;
} else{
.......
}
✍然后直接return没有其它操作
retrn handled;
为什么都是true会满足if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget)条件,
这就是我们上面说了的一个重要的方法: newTouchTarget = addTouchTarget(child, idBitsToAssign);
👇 addTouchTarget(child, idBitsToAssign)方法分析
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
接下来看TouchTarget.obtain(child, pointerIdBits)是啥:
private static final class TouchTarget { //✍TouchTarget 是ViewGroup内部类
public static TouchTarget obtain(@NonNull View child, int pointerIdBits) {
if (child == null) {
throw new IllegalArgumentException("child must be non-null");
}
final TouchTarget target;
synchronized (sRecycleLock) {
✍sRecycleBin 只有下面recycle()方法中初始化,每一层布局DOWN动作进来都是null
if (sRecycleBin == null) {
target = new TouchTarget(); ✍每层新建一个TouchTarget对象
} else {
target = sRecycleBin;
sRecycleBin = target.next;
sRecycledCount--;
target.next = null;
}
}
🔺很重要!把child保存到target.child中
target.child = child;
target.pointerIdBits = pointerIdBits;
return target;
}
public void recycle() {
......
synchronized (sRecycleLock) {
if (sRecycledCount < MAX_RECYCLED) {
sRecycleBin = this;
} else {
......
}
......
}
}
}
TouchTarget对象创建好了接下回到addTouchTarget 方法
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
//✍ mFirstTouchTarget此时为null,即target.next = null
target.next = mFirstTouchTarget;
//✍ 把TouchTarget赋值给mFirstTouchTarget
mFirstTouchTarget = target;
return target;
}
还记得 newTouchTarget = addTouchTarget(child, idBitsToAssign);即newTouchTarget = target;
所以 后面有一个判断条件if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget),target == newTouchTarget为true。
从接收事件动作的控件开始往外的每一层布局都执行一次 addTouchTarget(child, idBitsToAssign)方法,下面我举个例子看这样执行以后是啥样子:
自定义了两个LinearLayout分别为 MyLinearLayoutOut 和 MyLinearLayout;自定义一个Button:MyButton;布局如下:
因为Button默认接收消费事件,所以在xml布局文件中把android:clickable="false" ;
<com.sz.MyButton
android:id="@+id/my_btn"
android:clickable="false"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="MyButton" />
然后在MyLinearLayout 里面重写onTouchEvent方法让他接收处理事件:
@Override
public boolean onTouchEvent(MotionEvent event) {
return true;
}
其他的都是默认的。
我们点击MyButton按钮以后:
dispatchTouchEvent 从 DecorView ➞ MyLinearLayoutOut ➞ MyLinearLayout ➞ MyButton
然后开始判断 MyButton 是否接收事件,逐层往外返回如下图:
(PS1:图中TouchTarget@1862,TouchTarget@1850这个是随便写的表示分别是不同的TouchTarget对象而不是同一个)
(PS2:图中DecorView和MyLinearLayout之间还有布局但是为了方便就直接省略了)
(PS3:最外层还有一个Activity,没有加是因为它是有点特殊的我们后面讨论)
这样通过DOWN事件就给接收事件的MyLinearLayout的所有父布局里的 mFirstTouchTarget都赋值了,且TouchTarget.child就指向子布局。
但接收事件的MyLinearLayout它的mFirstTouchTarget = null。
这样DOWN事件就处理完了,接着就处理UP事件。