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事件分发源码归纳》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

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

游戏数据分析的艺术

游戏数据分析的艺术

于洋、余敏雄、吴娜、师胜柱 / 机械工业出版社 / 2015-7 / 79.00

《游戏数据分析的艺术》是中国游戏产业的开创性著作,具有里程碑意义,它首次系统讲解了如何对游戏行业的数据进行分析,在行业里竖起了一根标杆。作者是来自TalkingData等国内顶尖的数据分析机构和西山居这样的知名游戏公司的资深数据分析专家, 对游戏数据从不同的业务角度进行了诠释。本书详细剖析了游戏数据分析相关的指标、方法论、内容挖掘、数据挖掘、软件使用、游戏设计、运营策划、渠道推广、收入解读、用户分......一起来看看 《游戏数据分析的艺术》 这本书的介绍吧!

HTML 压缩/解压工具
HTML 压缩/解压工具

在线压缩/解压 HTML 代码

RGB转16进制工具
RGB转16进制工具

RGB HEX 互转工具

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具