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 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事件。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值