美文网首页Android自定义View
android事件分发源码分析—笔记

android事件分发源码分析—笔记

作者: 韩明泽 | 来源:发表于2019-02-18 23:24 被阅读60次

    昨天晚上从源码角度复习了一下android的事件分发机制,今天将笔记整理下放在网上。其实说复习,也是按着《android开发艺术探索》这本书作者的思路做的笔记。

    目录

    • 事件是如何从Activity传递到ViewGroup中的
    • ViewGoup对事件做了哪些操作
    • 事件在View中的处理

    1. 事件是如何从Activity传递到ViewGroup中的

    android的事件分发过程大致为Activity-->ViewGroup-->View,当我们点击屏幕的时候产生事件,系统会调用的ActivitydispatchTouchEvent()开始事件的传播过程,下面我们看一下Activity的dispatchTouchEvent()源码:

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            //当用户操作了按键或者触摸了屏幕,就会回调用该函数
            onUserInteraction();
        }
            //调用window的superDispatchTouchEvent()方法
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
    

    Activity的dispatchTouchEvent()在它的ACTION_DOWN事件中调用onUserInteraction()这个函数可以用来监听用户的一些按键和触摸操作。然后就会调用window的superDispatchTouchEvent()方法将事件传播到window中,如果window的superDispatchTouchEvent()返回true则代表事件被消耗了,反之代表事件没有被处理,这个时候Activity就会调用自己的onTouchEvent()来处理。

      事件传播到Window中该类是一个抽象类,它的superDispatchTouchEvent()方法是一个抽象方法。我们需要查看它的实现类PhoneWindow中查看事件的执行过程,下面为PhoneWindow的superDispatchTouchEvent()源码:

    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        //将事件传递给DecorView
        return mDecor.superDispatchTouchEvent(event);
    }
    

    PhoneWindow的superDispatchTouchEvent()中就一行代码,就是将事件传递给顶层View的superDispatchTouchEvent()。DecorView有将该方法传递给他父类dispatchTouchEvent()。如果读者对DecorView有所了解,应该知道DecorView是FrameLayout的一个子类,也就是说事件传递到了ViewGroup的dispatchTouchEvent()方法中了。

    2. ViewGoup对事件做了哪些操作

    ViewGroup的dispatchTouchEvent()方法代码很长,我们先看下面一本分代码:

    
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        //将之前的状态清除
        cancelAndClearTouchTargets(ev);
        resetTouchState();
    }
    // Check for interception.
    final boolean intercepted;
    if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
       //默认情况下他们两个比较的结果为0
        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
        if (!disallowIntercept) {
            intercepted = onInterceptTouchEvent(ev);
            ev.setAction(action); 
        } else {
            intercepted = false;
        }
    } else {
        intercepted = true;
    }
    

    从上面的代码我们可以知道,如果ViewGroup分发的是ACTION_DOWN事件,那么FLAG_DISALLOW_INTERCEPT这个标记位将会被重置,即使子View通过requestDisallowInterceptTouchEvent()设置了该标记为。也就是说分发下来的是ACTION_DOWN事件的话ViewGroup将会调用自己的onInterceptTouchEvent()寻问是否拦截。如果ViewGroup不拦截ACTION_DOWN事件,那么事件将会向它的子View传递:

    final View[] children = mChildren;
    for (int i = childrenCount - 1; i >= 0; i--){
        ...
        //通过dispatchTransformedTouchEvent()将事件传递给子View
        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();
            //对mFirstTouchTarget赋值
            newTouchTarget = addTouchTarget(child, idBitsToAssign);
            alreadyDispatchedToNewTouchTarget = true;
            break;
        }
        ev.setTargetAccessibilityFocus(false);
    }
    

    ViewGroup首先会遍历子所有View,然后判断子元素是否能接受这个点击事件。主要是通过两点,子元素是否在播放动画和点击事件的着落点是否在子元素的区域内。如果满足上面的两点那么事件将会传递给他处理。dispatchTransformedTouchEvent()实际上就是子元素的dispatchTouchEvent()方法。

    if (child == null) {
        handled = super.dispatchTouchEvent(event);
    } else {
        handled = child.dispatchTouchEvent(event);
    }
    

    如果子元素的dispatchTounchEvnent()返回的为true,那么mFirstTouchTarget将会赋值并且跳出循环,如果为false将会进入下次循环继续遍历子View。

    //对mFirstTouchTarget赋值
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
    alreadyDispatchedToNewTouchTarget = true;
    break;
    

    下面为addTouchTarget()函数的代码:

    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        //mFirstTouchTarget为链表结构
        mFirstTouchTarget = target;
        return target;
    }
    

    mFirstTouchTarget是否赋值,将会影响ViewGroup的拦截策略。如果mFirstTouchTarget为null,那么ViewGroup将会拦截下来同一序列的所有事件。那mFirstTouchTarget在什么情况下才为null呢?一般在两种情况下,要么是ViewGroup遍历了所有的子元素事件没有被处理;要么是子元素处理了ACTION_DOWN但是dispatchTouchEvent返回为false。这两种情况下ViewGroup会处理该事件,并且后续的事件也将交给他处理不再向子元素传递。

    if (mFirstTouchTarget == null) {
        // 出入的第三个参数为null,代表事件交给ViewGroup自己处理
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    }
    

    3. 事件在View中的处理

    View的事件处理比ViewGroup要简单,他首先会判断是否设置是否设置了onTouchListener()函数。

    下面为View的dispatchTouchEvent()部分代码:

    public boolean dispatchTouchEvent(MotionEvent event) {
         ...
    
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //首先判断是否设置了onTouchListener()
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            //调用onTouchEvent(event)
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
         ...
        return result;
    }
    

    从上面代码可知,如果我们设置了onTouchListener()那么它会调用onTouch()方法,并且onTouch()返回值将会影响对View的onTouchEvent(event)函数的调用,如果返回true将不会调用。由此可见onTouch()的优先级要高于onTouchEvent()。

    public boolean onTouchEvent(MotionEvent event) {
        ...
        //如果View设有代理,将会执行TouchDelegate.onTouchEvent(event)
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
        //只要View的CLICKABLE和LONG_CLICKABLE有一个返回true,他就会被消耗这个事件。
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                      ...
                            if (!focusTaken) {
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                //点击事件
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        ...
                    mIgnoreNextUpEvent = false;
                    break;
    
                case MotionEvent.ACTION_DOWN:
                    ...
                    //长安事件
                    if (!clickable) {
                        checkForLongClick(0, x, y);
                        break;
                    }
                    ...
                    break;
                    ...
            }
            return true;
        }
        return false;
    }
    

    从上面的代码可以看出,View的点击事件是在ACTION_UP事件中调用了performClick()方法处理,长按事件是在ACTION_DOWN事件中调用了checkForLongClick()方法处理。

    总结

    文章到这里也把android的分发机制从源码的角度分析的差不多了,读者应该对android的事件分发机制应该会有有一个比较全面的了解了。下篇文章我将记录一下关于事件冲突的笔记,如果感兴趣可以继续关注。

    参考

    Android艺术开发探索第三章————View的事件体系(下)

    相关文章

      网友评论

        本文标题:android事件分发源码分析—笔记

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