美文网首页
Android事件分发源码归纳

Android事件分发源码归纳

作者: 大棋17 | 来源:发表于2019-01-22 10:06 被阅读0次

    前提须知:</br>

           1、DecorView是activity的根布局。</br>
           2、activity的window是PhoneWindow实例。</br>
           3、DecorView是一个FrameLayout。</br>
           4、ViewGroup继承自View。没有重载View#onTouchEvent()方法。</br>
           5、源码版本为android 9.0。</br>

    一图胜千言(PhoneWindow、DecorView和Activity布局的关系)(ps:盗用下辉哥的图):


    image

    简单介绍:</br>

    ViewGroup:</br>
           1、ViewGroup#dispatchTouchEvent()    对ViewGroup的事件进行分发。</br>
          
    2、ViewGroup#onInterceptTouchEvent()    对ViewGroup的事件进行拦截。</br>
          
    3、ViewGroup#onTouchEvent()    对ViewGroup的事件进行处理(消费)。</br>

    View:</br>
           1、View#dispatchTouchEvent()    对View的事件进行分发。</br>
          
    2、View#onTouchEvent()    对View的事件进行处理(消费)。</br>

    MotionEvent:</br>
           1、MotionEvent.ACTION_DOWN    Down事件 用户手指按下时触发。</br>
          
    2、MotionEvent.ACTION_MOVE    Move事件 用户手指在屏幕上移动时触发</br>
          
    3、MotionEvent.ACTION_UP    Up事件 用户手指抬起,离开屏幕时触发</br>
          
    4、MotionEvent.ACTION_CANCEL    Cancel事件 </br>

    完整的MotionEvent = MotionEvent.ACTION_DOWN + n*MotionEvent.ACTION_MOVE + MotionEvent.ACTION_UP

    其他:</br>
          1、onTouchEvent()返回true时,表示该View或者ViewGroup消费此事件;返回false时,表示该View或者ViewGroup没有消费事件。</br>
          2、可对View或ViewGroup设置View.OnTouchListener进行触摸事件监听。OnTouchListener#onTouch()可以对事件进行监听并消费事件。

    测试Demo:

    image

          定义一个继承View的MyButton类,重载dispatchTouchEvent()和onTouchEvent(),并添加打印输出。</br>

          定义一个继承View的MyView类,重载dispatchTouchEvent()和onTouchEvent(),并添加打印输出。</br>

          定义一个继承FrameLayout的MyViewGroup类,重载dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent(),并添加打印输出。(继承LinearLayout不需要重载onLayout()方法摆放子View)</br>

    注意:</br>
          不可继承自Button,否则会默认消费事件。

    #MainActivity.kt
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            daqiBtn.setOnTouchListener(object :View.OnTouchListener{
                override fun onTouch(v: View?, event: MotionEvent?): Boolean {
                    when(event?.action){
                        MotionEvent.ACTION_DOWN -> {
                            Log.d("daqia","View OnTouchListener ACTION_DOWN")
                        }
                        MotionEvent.ACTION_MOVE -> {
                            Log.d("daqia","View OnTouchListener ACTION_MOVE")
                        }
                        MotionEvent.ACTION_UP -> {
                            Log.d("daqia","View OnTouchListener ACTION_UP")
                        }
                    }
                    return false
                }
            })
        }
    }
    
    #activity_main.xml
    <com.daqi.daqitouchevent.MyViewGroup
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent">
    
        <com.daqi.daqitouchevent.MyButton
            android:id="@+id/daqiBtn"
            android:layout_width="120dp"
            android:layout_height="50dp"
            android:layout_marginLeft="10dp"
            android:layout_marginTop="10dp"
            android:background="@color/colorAccent"/>
    </com.daqi.daqitouchevent.MyViewGroup>
    

    Activity:

          当手指触碰屏幕时,触摸事件最先达到Activity#dispatchTouchEvent()。所以我们先来看看Activity的dispatchTouchEvent()。

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            //空方法
            onUserInteraction();
        }
        //其实就是调用PhoneWindow的superDispatchTouchEvent()
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
    

          1、Down事件会执行onUserInteraction()方法。Activity#onUserInteraction()是一个空方法,而且翻阅了FragmentActivity和AppCompatActivity都没有发现重载onUserInteraction()方法。所以默认当初空方法处理。</br>

          2、Activity中的window是PhoneWindow实例,所以getWindow().superDispatchTouchEvent(ev)其实是调用PhoneWindow的superDispatchTouchEvent()方法。</br>

          跳转到PhoneWindow#superDispatchTouchEvent(),发现调用的是mDecor的superDispatchTouchEvent()方法,而mDecor就是DecorView的实例。

    #PhoneWindow.java
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }
    

          3、跳转到DecorView#superDispatchTouchEvent(),看到调用父类的方法super.dispatchTouchEvent(event)。</br>

          DecorView继承自FrameLayout,但FrameLayout没有重载dispatchTouchEvent(event)方法,即super.dispatchTouchEvent(event)调用的是ViewGroup#dispatchTouchEvent(event)

    public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
    

          所以,当DecorView#dispatchTouchEvent()返回false时,会调用Activity#onTouchEvent()处理(消费)该事件。否则表示布局中已有组件处理(消费)了事件。

    View:

          View作为最底层的"叶子",并不涉及太多的事件分发且没有拦截机制,所以先从View开始研究事件分发。</br>

    View#dispatchTouchEvent

    //只显示主要代码,其他代码用//....屏蔽掉
    public boolean dispatchTouchEvent(MotionEvent event) {
            //....
            boolean result = false;
            
            final int actionMasked = event.getActionMasked();
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Defensive cleanup for new gesture
                //1、down事件下来先停止嵌套滑动
                stopNestedScroll();
            }
    
            if (onFilterTouchEventForSecurity(event)) {
                //....
                
                //ListenerInfo中定义了一堆监听事件类变量,我们设置的view监听事件,都是存储到这里。
                //2、比如OnClickListener、OnLongClickListener等
                //noinspection SimplifiableIfStatement
                ListenerInfo li = mListenerInfo;
                
                //3、如果没有定义onTouch监听事件,则result为false,并直接执行onTouchEvent(event)
                //如果定义了onTouch监听事件,当mOnTouchListener.onTouch返回true时,result则为true,导致下面!result为false,onTouchEvent(event)无法被执行。
                //(mViewFlags & ENABLED_MASK) == ENABLED 判断组件是否为启动状态
                if (li != null && li.mOnTouchListener != null
                        && (mViewFlags & ENABLED_MASK) == ENABLED
                        && li.mOnTouchListener.onTouch(this, event)) {
                    result = true;
                }
    
                if (!result && onTouchEvent(event)) {
                    result = true;
                }
            }
    
            //....
            //4、
            return result;
        }
    

          1、先停止嵌套滑动</br>

          2、ListenerInfo中定义了很多View的监听事件实例,可从ListenerInfo中获取OnTouchListener实例,回调onTouch()方法。调用setOnTouchListener其实是将传进来的View.OnTouchListener赋值到ListenerInfo的OnTouchListener变量中。

          3、当li.mOnTouchListener != null时,会回调View.OnTouchListener#onTouch(),如果我们在onTouch()方法中返回true,表示消费此事件交由onTouch()处理(消费),不会调用View#onTouchEvent()来处理(消费)事件;反之,在onTouch()中返回false,表示不消费此事件,则事件交由onTouchEvent()处理(消费)。</br>

          当没有设置View.OnTouchListener监听时,result默认为false,事件默认交由View#onTouchEvent()处理(消费)。

          4、当View#onTouchEvent()或者OnTouchListener#onTouch()返回true时(两者只调用一个),View#dispatchTouchEvent()整体返回true。反之,View#dispatchTouchEvent()整体返回false。

    View#onTouchEvent

    public boolean onTouchEvent(MotionEvent event) {
        //1、获取view是否可以点击(点击,长按等)
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE 
                                   || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
        //2、判断当前组件是否处于非启动(禁用)状态,即enabled是否为false
        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;
        }
        //android有效触摸的范围是48dp,当组件太小,而又因为布局问题不能设置padding时。
        //3、可对父组件设置TouchDelegate,以增大点击面积。
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        //4、当组件可点击时,响应点击事件。
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
               case MotionEvent.ACTION_UP:
                    //..
                    break;
               case MotionEvent.ACTION_DOWN:
                    //..
                    break;
               case MotionEvent.ACTION_CANCEL:
                    //..
                    break;
               case MotionEvent.ACTION_MOVE:
                    //..
                    break;
            }
            //只要组件是可点击的,必定消费事件
            return true;
        }
        //5、组件不可点击的,则不会消费事件。
        return false
    }
    

          1、依据组件的设置点击监听和长按监听的情况,获取表示组件是否可点击的布尔值clickable。</br>

          2、当组件处于禁用(DISABLED)状态,返回clickable。如果组件可点击或长按,则会返回true消费事件,但不做处理。(该View处于非启动(禁用)状态,enabled为false)</br>

          3、当组件设置了TouchDelegate时,则会直接消费事件。(子View自身体积太小不利于点击,但又限制于布局无法增大自身大小,可对父组件设置TouchDelegate,以增大点击面积。TouchDelegate不在讨论范围)</br>

          4、如果View设置了点击监听或长按监听,则会依据事件进行不同的处理,但最后还是会return true消费事件。(switch语句结束后返回true)</br>

          5、否则View没有设置点击监听或长按监听,则return false不消费事件。</br>

          注意:</br>
                1、onTouchLinstener在View#dispatchTouchEvent()中回调,如果重载dispatchTouchEvent,而不调用父类的dispatchToucEvent,则不会有onTouch()回调。</br>
                2、onLongClickListener和onClickListener在View#onTouchEvent()中回调,同理,不调用父类的onTouchEvent(),则不会有onClick()和onLongClick()回调。

    ViewGroup:

          ViewGroup中涉及到更多事件分发和事件拦截机制,会比较难懂。先看一遍源码,再依据demo中的现象在源码中寻找答案,从而更好的学习和加深对源码的认识。

    ViewGroup#dispatchTouchEvent()

    #ViewGroup.java
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //....
    
        // Handle an initial down.
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            //1、在开始新的触摸手势时丢弃所有先前的状态,重置Touch状态标识
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }
        
        //标记是否拦截事件
        final boolean intercepted;
        //2、down事件或已经有处理Touch事件的目标了
        //当子控件没有消费事件或者被拦截mFirstTouchTarget为空
        if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
            //3、标记是否禁止拦截
            //因为在其他地方可能调用了requestDisallowInterceptTouchEvent(boolean disallowIntercept)
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                //回调拦截方法
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            //4、
            intercepted = true;
        }
        
        //...
    }
    

          1、Down事件是整套MotionEvent事件的开始,先丢弃所有先前的状态,重置Touch状态标识。</br>

          2、actionMasked == MotionEvent.ACTION_DOWN
    || mFirstTouchTarget != null    事件为down事件时,等式成立,进入if语句。</br>

          3、获取一个布尔值disallowIntercept(字面意思:不允许拦截),该布尔值决定着调不调用ViewGroup#onInterceptTouchEvent(),而ViewGroup#onInterceptTouchEvent()正是用来拦截事件的。</br>

          disallowIntercept赋值true还是false,由当前ViewGroup中的子View决定。</br>
          当子View调用requestDisallowInterceptTouchEvent(boolean)方法请求父类不要拦截时,该disallowIntercept为true,标记是否拦截的布尔值intercepted写死为false,表示不拦截。</br>
          当没有子View调用requestDisallowInterceptTouchEvent(boolean)方法时,该disallowIntercept为false,回调ViewGroup#onInterceptTouchEvent()。ViewGroup可以在onInterceptTouchEvent()中对事件进行拦截。</br>

          4、当不是Down事件 和 mFirstTouchTarget为null时,intercepted = true。先记住,后面用到。(重点mFirstTouchTarget为null)</br>

    #ViewGroup.java
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
            
        //...
        
        //每次分发事件前,将newTouchTarget置空
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        //5、不是取消状态且没拦截的情况下
        if (!canceled && !intercepted) {
            //....
            
            //6、判断(x,y)在该View的可视范围内。true则继续往下执行。
            if (!canViewReceivePointerEvents(child)
                || !isTransformedTouchPointInView(x, y, child, null)) {
                ev.setTargetAccessibilityFocus(false);
                continue;
            }
            //....
            
            final View[] children = mChildren;
            
            //7、遍历子view
            for (int i = childrenCount - 1; i >= 0; i--) {
                //....
                
                //通过dispatchTransformedTouchEvent()将事件分发下去
                //dispatchTransformedTouchEvent返回true时,表示子view消费了事件
                //当第三参数传child(View实例)时,调用该ViewGroup的子View的dispatchTouchEvent()
                //当第三参数传null时,调用父类(即View)的dispatchTouchEvent()
                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                    // Child wants to receive touch within its bounds.
                    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();
                    //8、对mFirstTouchTarget进行赋值
                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                    //标记已派发事件给新的触摸对象
                    alreadyDispatchedToNewTouchTarget = true;
                    break;
                }
                
                //...
            }
            
            //9、如果遍历完子View,newTouchTarget没有被赋值,即本次没有消费该事件的子View,但之前有子View的消费事件记录(mFirstTouchTarget 不为空)
            if (newTouchTarget == null && mFirstTouchTarget != null) {
                // Did not find a child to receive the event.
                // Assign the pointer to the least recently added target.
                //该事件没有找到触摸对象,但之前有触摸对象,则将新的触摸对象指向最近的触摸对象
                newTouchTarget = mFirstTouchTarget;
                //从链表表头触摸对象开始遍历,寻找下一个触摸对象,指定找到最后的(第一个)触摸对象为止,将其赋值给newTouchTarget变量
                while (newTouchTarget.next != null) {
                    newTouchTarget = newTouchTarget.next;
                }
                newTouchTarget.pointerIdBits |= idBitsToAssign;
            }
        }
        
        //....
    }
    

          5、上面对定义的是否拦截的布尔值intercepted,在判断语句 !canceled && !intercepted 用上了。if(!canceled && !intercepted)内会对事件进行事件分发。intercepted为true时,表明事件被该ViewGroup拦截了,不会将事件分发给该ViewGroup的子View。</br>

          6、ViewGroup#isTransformedTouchPointInView()用于判断该事件的坐标(x,y)是否在子View可视范围内。符合条件才继续往下执行。

          7、遍历自己的子View,通过dispatchTransformedTouchEvent()将事件分发出去。dispatchTransformedTouchEvent返回true时,表示子view消费了事件。(第三个参数传递了当前遍历的子View)</br>

          子View的遍历是倒序来得,其实是先选择点击范围内的顶层View,再一层一层将事件分发下去。

    #ViewGroup.java
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                View child, int desiredPointerIdBits) {
            
        //....
    
        // Perform any necessary transformations and dispatch.
        //child为空 调用super.dispatchTouchEvent(),即调用View#dispatchTouchEvent(),而View#dispatchTouchEvent()中会调用View#onTouchEvent()
        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());
            }
            
            //调用子View的dispatchTouchEvent(),将事件分发下去。
            handled = child.dispatchTouchEvent(transformedEvent);
        }
    
        //....
    }
    

          当第三参数传 child(View实例) 时,调用该子View的dispatchTouchEvent()。</br>

          当第三参数传 null 时,调用父类(即View)的dispatchTouchEvent()。而View#dispatchTouchEvent中,会调用View#onTouchEvent()。即调用自身的onTouchEvent(),因为ViewGroup没有重载onTouchEvent()。</br>

          8、dispatchTransformedTouchEvent()的返回值作为if语句的判断条件。当if语句为true时,说明子View消费事件。然后调用addTouchTarget(),将子View封装成TouchTarget,并将其赋值到mFirstTouchTarget。break退出循环,不再分发Move事件。(也就是说只把事件分发给一个消费该事件的子View)</br>

    #ViewGroup.java
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        //对mFirstTouchTarget进行赋值
        mFirstTouchTarget = target;
        return target;
    }
    

          TouchTarget是一个链表解构,可以看到将target最终会赋值到mFirstTouchTarget,而上一步是将target的下一个索引指向mFirstTouchTarget,也就是说target的下一个索引指向的是上一个消费事件的target,而本次消费事件的target作为链表的第一个元素。</br>

          然后将alreadyDispatchedToNewTouchTarget设为true,标记已分发事件给新的TouchTarget</br>

          9、遍历完子View,newTouchTarget仍然没有被赋值,即本次子View没有消费事件。但mFirstTouchTarget 不为空,表示之前有子View的消费事件的记录。则遍历链表,找出第一个消费事件的target,将其赋值给newTouchTarget对象。

    #ViewGroup.java
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
    
        //....
            
        if (mFirstTouchTarget == null) {
            // No touch targets so treat this as an ordinary view.(没有触摸目标,所以将其视为普通视图。)
            //10、即ViewGroup调用父类(View)的dispatchTouchEvent()
            // 如果View的dispatchTouchEvent中返回true,消费了此事件,则handled会为true,ViewGroup#dispatchTouchEvent()返回true。表示告诉该ViewGroup的父视图,该视图消费了事件。
            // 否则,无子View消费事件,自身也不消费事件的情况下,则该ViewGroup#dispatchTouchEvent()返回false,表示不消费事件
            // 因为模版设计模式,通过View的dispatchTouchEvent进行对ViewGroup的onTouch() 和 onTouchEvent()进行调用。 
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                                                    TouchTarget.ALL_POINTER_IDS);
        } else {
        
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            //11、遍历TouchTargt树,分发事件
            while (target != null) {
                final TouchTarget next = target.next;
                //如果新的触摸目标依据派发过事件 且当前触摸目标等于新的触摸目标。
                //alreadyDispatchedToNewTouchTarget在上面子View消费会进行赋值为true
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    //既然子View依据消费了事件,则ViewGroup#dispatchEvent()必定返回true
                    handled = true;
                } else {
                    //标记是否让child取消处理事件
                    final boolean cancelChild = resetCancelNextUpFlag(target.child)
                        || intercepted;
                    //派发事件
                    if (dispatchTransformedTouchEvent(ev, cancelChild,
                                                      target.child, target.pointerIdBits)) {
                        //ViewGroup#dispatchEvent()返回true
                        handled = true;
                    }
                    //12、cancelChild为true时,将当前target从TouchTarget链表中移除
                    if (cancelChild) {
                        if (predecessor == null) {
                            mFirstTouchTarget = next;
                        } else {
                            predecessor.next = next;
                        }
                        target.recycle();
                        target = next;
                        continue;
                    }
                }
                predecessor = target;
                target = next;
            }
        }
    }
    

          10、如果mFirstTouchTaget没有被赋值,则表示本次包括之前的,没有子View消费事件。调用ViewGroup#dispatchTransformedTouchEvent(),第三个参数传递null,调用自身的onTouch()和onTouchEvent()。自己尝试消费事件,如果自身不消费事件,则ViewGroup#dispatchTouchEvent()返回false。</br>

          11、如果mFirstTouchTaget不为空,则遍历TouchTaget链表。</br>

          alreadyDispatchedToNewTouchTarget为true只有在本次事件被子View消费的情况下不为空。即调用addTouchTaget()方法后设置为true。</br>

          但newTouchTarget存在两种情况不为空:</br>
                一种是本次事件被子View消费了,调用了addTouchTaget();</br>
                一种是本次事件没有被消费,但之前有子View消费事件(可见ViewGroup#dispatchTouchTaget分析第9条)</br>

          当本次事件被子View消费时,alreadyDispatchedToNewTouchTarget为true,且newTouchTarget等于mFirstTouchTarget。</br>
    alreadyDispatchedToNewTouchTarget && target == newTouchTarget条件成立,因为target初始赋值是mFirstTouchTarget。则直接将ViewGroup#dispatchTouchEvent()返回值设置为true。

          alreadyDispatchedToNewTouchTarget && target == newTouchTarget条件不成立时,获取一个标记是否让child取消处理事件的布尔值cancelChild,同时以第二个参数传入ViewGroup#dispatchTransformedTouchEvent()。</br>

          cancelChild的值由resetCancelNextUpFlag(target.child) || intercepted 决定。resetCancelNextUpFlag()看不懂,但当ViewGroup拦截事件时,intercepted = true,则cancelChild = true。</br>

          当布尔值cancel为true时,也就是cancelChild为true时,会将事件修改为MotionEvent.ACTION_CANCEL,在分发给子View。然后复原MotionEvent。并将target从TouchTarget链表中移除。</br>

          当布尔值cancel为false时,也就是cancelChild为false时,会将事件原封不动的分发给子View。走正常分发的流程。</br>

    #ViewGroup.java
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {
        final boolean handled;
        
        //标记当前属于的MotionEvent类型
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            //将事件改为MotionEvent.ACTION_CANCEL,并分发给子View
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            //复原当前的MotionEvent类型
            event.setAction(oldAction);
            return handled;
        }
        
        //....
    }
    

    总得来说就是:</br>
          遍历TouchTarget链表,当本次事件已经被子View消费时,则将返回值设为true,表示本ViewGroup尝试消费该事件。</br>
          判断当前是否发送MotionEvent.ACTION_CANCEL事件,如果intercepted为true导致cancelChild为true时,将事件改成ACTION_CANCEL事件,发送给链表每一个元素,同时将其从TouchTarget链表中删除。否则cancelChild为false时,将事件原封不动的发送给链表中的元素。</br>
          当链表中的元素消费事件时,则将ViewGroup#dispatchTouchEvent()的返回值改为true。</br>

    示例1、

          正常的重载ViewGroup和View关于事件分发的方法,不做任何改动,但对事件进行打印输出。

    image

    Down和Up事件传递的顺序是:</br>
                ViewGroup#dispatchTouchEvent() -> ViewGroup#onInterceptTouchEvent() -> View#dispatchTouchEvent -> View#onTouchEvent() -> ViewGroup#onTouchEvent()

    现象:</br>
          手指按下去后,无论怎么移动,然后抬起都是只有这5个打印。从打印看只触发了Down事件,没有Move事件和Up事件。并传递给自己的子View。

    解析:</br>
          dowm事件时,actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null条件是成立的。所以intercepted依据ViewGroup#onInterceptTouchEvent()返回值来赋值,ViewGroup#onInterceptTouchEvent()默认返回false。</br>

          条件!canceled && !intercepted成立时,遍历子View,通过dispatchTransformedTouchEvent(child)对事件进行分发。即调用子View的dispatchTouchEvent()方法和View#onTouchEvent()方法。

          当事件没有子View消费时,if(dispatchTransformedTouchEvent(child))条件不成立,导致mFirstTouchTarget依旧为空。

          继续往下执行,到if (mFirstTouchTarget == null)时,条件成立。则调用dispatchTransformedTouchEvent(null)。传入的子View为null,调用父类也就是View的dispatchTouchEvent(),对ViewGroup的onTouchEvent()进行调用(ViewGroup没重载onTouchEvent())。

          move事件时,由于已经不是down事件了,且mFirstTouchTarget依旧为空。则intercepted被设置为true。if(!canceled && !intercepted)条件不成立,无法进入该if语句中调用dispatchTransformedTouchEvent(child)对move事件进行分发。子View也就不会再接受到后续的事件。

    疑问:</br>
          不分发给View事件,但ViewGroup#dispatchTouchEvent()依据被调用,为什么没打印。</br>
          因为该ViewGroup不是根布局,相对于DecorView,自定义的ViewGroup属于子View。是DecorView不分发事件下来,并且我们也没有消费事件。

    示例2、

          正常的重载ViewGroup和View关于事件分发的方法,在其中一个View#onTouchEvent()的down事件时返回true,并对事件进行打印输出。

    image

    现象:</br>
          donw事件中,事件都分发到ViewGroup的全部子View中。当子View2返回true消费事件后,后续事件都只返回给该子View组件。

    解析:</br>
          按照正常的事件分发流程,mFirstTouchTarget指向的child是子View2。当move事件下来时,intercepted由onInterceptTouchEvent()赋值,默认为false,条件if(!canceled && !intercepted)成立。但后续判断语句不成立。actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE(通过debug发现的,但看不懂)。不符合再次将事件全部分发给子View的情况。</br>

          离开if(!canceled && !intercepted)语句。直接遍历TouchTarget链表,将事件分发给链表中的每一个元素。子View2在down事件时消费了事件,固存在与链表中,所以后续的事件都会分发给子View2。

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (!canceled && !intercepted) {
            //不满足条件时表示不需要再重新寻找响应事件的View(看不懂)
            if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            }
        }
    }
    
    示例3、

          正常的重载ViewGroup和View关于事件分发的方法,在其中一个View#onTouchEvent()的down事件时返回true,ViewGroup在第一个move事件时拦截事件,并对事件进行打印输出。


    image

    现象:</br>
          子View在onTouchEvent中return true,后续事件都会返回到自定义的ViewGroup中,但在第一个move事件时,ViewGroup#onInterceptTouchEvent()返回true将事件拦截了,交由ViewGroup处理事件,后续的事件都交由ViewGroup处理。同时子View接受到CANCEL事件。

    解析:</br>
          ViewGroup#onInterceptTouchEvent()返回true时,intercepted也为true。if(!canceled && !intercepted)条件不成立,直接遍历TouchTarget链表。</br>

          但此时intercepted也为true,cancelChild = resetCancelNextUpFlag(target.child) || intercepted,也就是说cancelChild为true,当cancelChild第二个参数传入dispatchTransformedTouchEvent()时,会给子View发送CANCEL事件,并将该子View从链表中删除。直至到TouchTarget清空。</br>

          当TouchTarget被清空后,mFirstTouchTarget为null,后续只调用ViewGroup#onTouchEvent()尝试消费事件。

    总结归纳图:

    image

    遗留问题:</br>
          为什么子View消费了事件,mFirstTouchTarget!= null,在move事件时if(actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE)不成立?</br>

          按debug看到的结果,直接跳到遍历TouchTarget链表来将后续的事件分发。不再通过分发事件寻找消费事件的子View,直接根据TouchTarget链表将事件分发下去可以理解。但链表什么情况下才会存在更多的元素,并且这段代码是在什么情况下才被使用到?希望大佬们指点下。

    if (newTouchTarget == null && mFirstTouchTarget != null) {
        // Did not find a child to receive the event.
        // Assign the pointer to the least recently added target.
        newTouchTarget = mFirstTouchTarget;
        while (newTouchTarget.next != null) {
            newTouchTarget = newTouchTarget.next;
        }
        newTouchTarget.pointerIdBits |= idBitsToAssign;
    }
    
    image

    相关文章

      网友评论

          本文标题:Android事件分发源码归纳

          本文链接:https://www.haomeiwen.com/subject/ygtljqtx.html