本文主要是介绍Android事件传递(二):DOWN 在Activity、View、ViewGroup传递,除了自己本身的传递,还做了什么?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
Android事件传递(一):Activity、View、ViewGroup及dispatchtouchEvent、onTouchEvent梳理
Android事件传递(三):事件动作 UP 在Activity、View、ViewGroup传递
Android事件传递(四):总结篇
下面源码基于Android11 API30
接上一篇文章,我们从Activity开始分析ACTION_DOWN动作的传递:
1 Activity#dispatchTouchEvent
public class Activity extends ContextThemeWrapperimplements Window.Callback,...... { ......省略其它代码......private Window mWindow;public Window getWindow() {return mWindow;}@UnsupportedAppUsagefinal 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 {......@Overridepublic boolean superDispatchTouchEvent(MotionEvent event) {✍mDecor即DecorViewreturn mDecor.superDispatchTouchEvent(event);}}
👇 DecorView.java
public class DecorView extends FrameLayoutimplements RootViewSurfaceTaker, ......{......public boolean superDispatchTouchEvent(MotionEvent event) {✍ DecorView extends FrameLayout,间接继承ViewGroup所以接着去看ViewGroup的dispatchTouchEventreturn super.dispatchTouchEvent(event);}}
👇 ViewGroup.java
@UiThread
public abstract class ViewGroup extends View implements ViewParent, ViewManager {......省略其它代码......@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {......省略其它代码......//✍默认值falseboolean 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 {//✍如果禁止父布局拦截事件,直接置为falseintercepted = 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 transformedEventtransformedEvent = 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.dispatchTouchEventhandled = 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 gesturestopNestedScroll();}//✍防止程序恶意遮盖,误导用户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方法返回truePS:点进 setOnTouchListener(onTouchListener)方法就是li.mOnTouchListener = onTouchListenerresult = 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://✍再判断一次是否可点击,若否直接breakif (!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 awaysetPressed(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 {@Overridepublic 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 {......省略其它代码......@Overridepublic 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 indexfor (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);✍开关置为truealreadyDispatchedToNewTouchTarget = true;break;}ev.setTargetAccessibilityFocus(false);}//for (int i = childrenCount - 1; i >= 0; i--) endif (newTouchTarget == null && mFirstTouchTarget != null) {✍当右手指按在屏幕上某个控件位抬起,另一手指按在另一控件但是该控件未接收事件,就会把第二根手指的事件绑定到前一个最新的控件上newTouchTarget = mFirstTouchTarget;while (newTouchTarget.next != null) {newTouchTarget = newTouchTarget.next;}newTouchTarget.pointerIdBits |= idBitsToAssign;}}//if (!canceled && !intercepted) endif (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动作进来都是nullif (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 = nulltarget.next = mFirstTouchTarget;//✍ 把TouchTarget赋值给mFirstTouchTargetmFirstTouchTarget = 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.MyButtonandroid:id="@+id/my_btn"android:clickable="false"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="MyButton" />
然后在MyLinearLayout 里面重写onTouchEvent方法让他接收处理事件:
@Overridepublic 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事件。
这篇关于Android事件传递(二):DOWN 在Activity、View、ViewGroup传递,除了自己本身的传递,还做了什么?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!