Android事件分发源码归纳

栏目: 数据库 · 发布时间: 5年前

内容简介:1、DecorView是activity的根布局。2、activity的window是PhoneWindow实例。3、DecorView是一个FrameLayout。

1、DecorView是activity的根布局。

2、activity的window是PhoneWindow实例。

3、DecorView是一个FrameLayout。

4、ViewGroup继承自View。没有重载View#onTouchEvent()方法。

5、源码版本为android 9.0。

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

Android事件分发源码归纳

ViewGroup:

1、ViewGroup#dispatchTouchEvent()    对ViewGroup的事件进行分发。

2、ViewGroup#onInterceptTouchEvent()    对ViewGroup的事件进行拦截。

3、ViewGroup#onTouchEvent()    对ViewGroup的事件进行处理(消费)。

View:

1、View#dispatchTouchEvent()    对View的事件进行分发。

2、View#onTouchEvent()    对View的事件进行处理(消费)。

MotionEvent:

1、MotionEvent.ACTION_DOWN    Down事件 用户手指按下时触发。

2、MotionEvent.ACTION_MOVE    Move事件 用户手指在屏幕上移动时触发

3、MotionEvent.ACTION_UP    Up事件 用户手指抬起,离开屏幕时触发

4、MotionEvent.ACTION_CANCEL    Cancel事件

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

其他:

1、onTouchEvent()返回true时,表示该View或者ViewGroup消费此事件;返回false时,表示该View或者ViewGroup没有消费事件。

2、可对View或ViewGroup设置View.OnTouchListener进行触摸事件监听。OnTouchListener#onTouch()可以对事件进行监听并消费事件。

测试Demo:

Android事件分发源码归纳

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

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

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

注意:

不可继承自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()方法。所以默认当初空方法处理。

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

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

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

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

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开始研究事件分发。

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、先停止嵌套滑动

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()处理(消费)。

当没有设置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。

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

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

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

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

注意:

1、onTouchLinstener在View#dispatchTouchEvent()中回调,如果重载dispatchTouchEvent,而不调用父类的dispatchToucEvent,则不会有onTouch()回调。

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状态标识。

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

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

disallowIntercept赋值true还是false,由当前ViewGroup中的子View决定。

当子View调用requestDisallowInterceptTouchEvent(boolean)方法请求父类不要拦截时,该disallowIntercept为true,标记是否拦截的布尔值intercepted写死为false,表示不拦截。

当没有子View调用requestDisallowInterceptTouchEvent(boolean)方法时,该disallowIntercept为false,回调ViewGroup#onInterceptTouchEvent()。ViewGroup可以在onInterceptTouchEvent()中对事件进行拦截。

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

#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。

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

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

子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()。

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

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

#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作为链表的第一个元素。

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

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。

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

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

但newTouchTarget存在两种情况不为空:

一种是本次事件被子View消费了,调用了addTouchTaget();

一种是本次事件没有被消费,但之前有子View消费事件(可见ViewGroup#dispatchTouchTaget分析第9条)

当本次事件被子View消费时,alreadyDispatchedToNewTouchTarget为true,且newTouchTarget等于mFirstTouchTarget。

alreadyDispatchedToNewTouchTarget && target == newTouchTarget条件成立,因为target初始赋值是mFirstTouchTarget。则直接将ViewGroup#dispatchTouchEvent()返回值设置为true。

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

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

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

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

#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;
    }
    
    //....
}
复制代码

总得来说就是:

遍历TouchTarget链表,当本次事件已经被子View消费时,则将返回值设为true,表示本ViewGroup尝试消费该事件。

判断当前是否发送MotionEvent.ACTION_CANCEL事件,如果intercepted为true导致cancelChild为true时,将事件改成ACTION_CANCEL事件,发送给链表每一个元素,同时将其从TouchTarget链表中删除。否则cancelChild为false时,将事件原封不动的发送给链表中的元素。

当链表中的元素消费事件时,则将ViewGroup#dispatchTouchEvent()的返回值改为true。

示例1、

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

Android事件分发源码归纳

Down和Up事件传递的顺序是:

ViewGroup#dispatchTouchEvent() -> ViewGroup#onInterceptTouchEvent() -> View#dispatchTouchEvent -> View#onTouchEvent() -> ViewGroup#onTouchEvent()

现象:

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

解析:

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

条件!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也就不会再接受到后续的事件。

疑问:

不分发给View事件,但ViewGroup#dispatchTouchEvent()依据被调用,为什么没打印。

因为该ViewGroup不是根布局,相对于DecorView,自定义的ViewGroup属于子View。是DecorView不分发事件下来,并且我们也没有消费事件。

示例2、

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

Android事件分发源码归纳

现象:

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

解析:

按照正常的事件分发流程,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的情况。

离开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事件时拦截事件,并对事件进行打印输出。

Android事件分发源码归纳

现象:

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

解析:

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

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

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

总结归纳图:

Android事件分发源码归纳

遗留问题:

为什么子View消费了事件,mFirstTouchTarget!= null,在move事件时if(actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE)不成立?

按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;
}
复制代码

demo地址:链接: pan.baidu.com/s/1dRNiQXAt… 提取码: jepe


以上所述就是小编给大家介绍的《Android事件分发源码归纳》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数学模型选谈

数学模型选谈

华罗庚//王元 / 大连理工大学出版社 / 2011-5 / 25.00元

《走向数学丛书03-数学模型选谈》,本书主要介绍了关于在等高线图上计算矿藏储量与坡地面积的问题、挂轮问题、挂轮问题、优选法(单、多因素)、黄金数与数值积分和统筹方法等数学模型问题。一起来看看 《数学模型选谈》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具