美文网首页
【Android源码】View的事件分发

【Android源码】View的事件分发

作者: 感同身受_ | 来源:发表于2020-12-05 11:14 被阅读0次

    目录:

    QWQIX7REKZF%38DNA$G%(GP.png

    一、 setContentView

    Activity中setContentView的源码,主要作用就是:

    生成DecorView,并把通过Activity的theme和Java代码里面设置的Feature匹配得到的layoutResource与DecorView绑定,然后再把我们传进去的layoutResId添加到DecorView上,最后再添加一个onContentChange()的回调

    二、 View的事件分发机制

    用户的点击事件会被系统封装成一个类:MotionEvent,当MotionEvent产生后,就会在各层View之间传递,这个传递过程就是点击事件分发

    其中,事件分发其主要作用的是三个方法:

    • dispatchTouchEvent() —— 进行事件分发
    • onInterceptTouchEvent() —— 进行事件拦截,在dispatchTouchEvent()中调用,但View没有提供这个方法(因为View一般就是最后一层,此时不必再对事件进行拦截,事件不会再继续传递下去)
    • onTouchEvent() —— 用来处理点击事件,在dispatchTouchEvent()中进行调用

    1. View的事件分发机制

    产生点击事件后,事件会从Activity ==> PhoneWindow ==> DecorView => ViewGroup ==> (...ViewGroup...) ==> View

    事件就这样一层一层的从上往下传递,我们直接从ViewGroup的dispatchTouchEvent()开始看。

    ViewGroup.java

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //...
        // Handle an initial down.
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Throw away all previous state when starting a new touch gesture.
            // The framework may have dropped the up or cancel event for the previous gesture
            // due to an app switch, ANR, or some other state change.
            cancelAndClearTouchTargets(ev);
            //初始化之前的状态
            resetTouchState();
        }
    
        // Check for interception.
        final boolean intercepted;
        //这里使用了mFirstTouchTarget这个属性
        if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
            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 {
            // There are no touch targets and this action is not an initial down
            // so this view group continues to intercept touches.
            intercepted = true;
        }
        //...
        return handled;
    }
    

    在dispatchTouchEvent()方法里,会先事件进行判断,看看是否为DOWN,如果是,则调用resetTouchState()方法。因为ACTION_DOWN事件是新事件的开始,所以需要调用resetTouchState()方法初始化之前的状态

    private void resetTouchState() {
        clearTouchTargets();
        //...
    }
    
    private void clearTouchTargets() {
        TouchTarget target = mFirstTouchTarget;
        if (target != null) {
            do {
                TouchTarget next = target.next;
                target.recycle();
                target = next;
            } while (target != null);
            mFirstTouchTarget = null;
        }
    }
    

    这里会对mFirstTouchTarget进行赋null值的操作。而mFirstTouchTarget是用来标记当前ViewGroup是否拦截了事件,如果拦截:mFirstTouchTarget=null,如果不拦截并交给子View来处理:mFirstTouchTarget!=null

    而mFirstTouchTarget在dispatchTouchEvent()方法中继续用来作为判断,==假设此时事件被拦截了==,那么mFirstTouchTarget != null为false,如果当前点击事件为ACTION_DOWN,那么就会就会调用onInterceptTouchEvent()方法,如果当前事件是ACTION_UPACTION_MOVE,那么就会直接执行intercepted = true;,此后的事件都将交由这个ViewGroup处理

    //当事件被拦截时,即 mFirstTouchTarget != null 为false,此时只有ACTION_DOWN事件,才会去调用onInterceptTouchEvent()方法
    if (actionMasked == MotionEvent.ACTION_DOWN
            || mFirstTouchTarget != null) {
        //FLAG_DISALLOW_INTERCEPT:留给子View去禁止ViewGroup拦截
        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 {
        //事件被拦截时,ACTION_UP和ACTION_MOVE都将会进到这里
        intercepted = true;
    }
    
     public boolean onInterceptTouchEvent(MotionEvent ev) {
         if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
             && ev.getAction() == MotionEvent.ACTION_DOWN
             && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
             && isOnScrollbarThumb(ev.getX(), ev.getY())) {
             return true;
         }
         //默认返回false
         return false;
     }
    

    除了ViewGroup自己去拦截ACTION_UPACTION_MOVE事件外,子View还可以通过FLAG_DISALLOW_INTERCEPT标志位来禁止ViewGroup拦截ACTION_UPACTION_MOVE事件,途径就是通过子View调用requestDisallowInterceptTouchEvent()这个方法来改变标记位

    onInterceptTouchEvent()方法默认返回false,不拦截事件,如果想要拦截,可以自定义ViewGroup重写这个方法

    我们继续回到ViewGroup的dispatchTouchEvent()上面来,看看事件是如何被分发给子View的

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //...
        final View[] children = mChildren;
        //遍历子元素(倒序遍历),从最上层的子View往内层遍历
        for (int i = childrenCount - 1; i >= 0; i--) {
            final int childIndex = getAndVerifyPreorderedIndex(
                childrenCount, i, customOrder);
            final View child = getAndVerifyPreorderedView(
                preorderedList, children, childIndex);
    
            //判断子View是否能获取到点击事件
            if (childWithAccessibilityFocus != null) {
                if (childWithAccessibilityFocus != child) {
                    //子View能接收到点击事件点击事件,交由子View处理
                    continue;
                }
                childWithAccessibilityFocus = null;
                //双重迭代
                i = childrenCount - 1;
            }
    
            //判断触摸点位置是否在子View的范围内,或者子View是否在播放动画
            if (!child.canReceivePointerEvents()
                || !isTransformedTouchPointInView(x, y, child, null)) {
                ev.setTargetAccessibilityFocus(false);
                //该子View不符合上面两个条件,开始遍历下一个
                continue;
            }
        }
        //...
    
        resetCancelNextUpFlag(child);
        //dispatchTransformedTouchEvent()方法里面会对View的事件分发进行处理
        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
            //...
        }
        ev.setTargetAccessibilityFocus(false);
        //...
        return handled;
    }
    
    
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                View child, int desiredPointerIdBits) {
            final boolean handled;
    
        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            //如果有子View,就去调用子View的dispatchTouchEvent()方法,如果没有子View,就去调用ViewGroup的父类——View的dispatchTouchEvent方法
            if (child == null) {
                //调用父类——View里面的dispatchTouchEvent()方法
                handled = super.dispatchTouchEvent(event);
            } else {
                //调用子View里面的dispatchTouchEvent()方法
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        //...
    }
    

    ViewGroup的dispatchTouchEvent()在遍历完children之后,就开始对时间进行分发,dispatchTransformedTouchEvent()里面就是对事件分发的处理,他会先去查看自己(ViewGroup)有没有子View,有就调用自己的子View去处理,没有就交给自己的父类——View处理,所以最终都是调用到了View去处理dispatchTouchEvent()

    1)View的dispatchTouchEvent():

    我们来看View.java里面的这个方法

    public boolean dispatchTouchEvent(MotionEvent event) {
        
        boolean result = false;
        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            //把所有的监听事件封装成了一个对象,这里面存放了View所有Listener信息,如onTouchListener
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED  //是否是enable,如果View设置了android:enabled="false",就都不能执行了
                    && li.mOnTouchListener.onTouch(this, event)) {  //如果你mOnTouchListener返回的是false,那么result就为false
                result = true;
            }
            //这里的onTouchEvent()能不能执行,完全取决于onTouch()方法的返回值,所以onTouch()方法的优先级大于onTouchEvent()
            if (!result && onTouchEvent(event)) {//如果result = false,就会执行onTouchEvent,如果result = true,就不会执行就会执行onTouchEvent
                result = true;
            }
        }
        //返回
        return result;
    }
    
    boolean isAccessibilityFocusedViewOrHost() {
        return isAccessibilityFocused() || (getViewRootImpl() != null && getViewRootImpl()
                .getAccessibilityFocusedHost() == this);
    }
    
    static class ListenerInfo {
        protected OnFocusChangeListener mOnFocusChangeListener;
        private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners;
        protected OnScrollChangeListener mOnScrollChangeListener;
        private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners;
        public OnClickListener mOnClickListener;
    }
    

    这个方法里面,其实就是利用 ==短路与== 的特性:当前面的条件不符合时,就不再判断后面的条件了,所以就通过这种方式,让enable属性控制mOnTouchListener方法的执行,和让result变量控制onTouchEvent的执行

    onTouch()方法之前,会先判断View的enable属性,如果enable被设置了android:enabled="false",那么后面的onTouch()、onTouxhEvent()、onClick()都不会得到执行。

    2)View的onTouchEvent():

    我们看源码:

    public boolean onTouchEvent(MotionEvent event) {
        //只要View的CLICKABLE和LONG_CLICKABLE有一个为true,clickable就会为true,那么onTouchEvent就会返回true,消耗这个事件
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
        
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
        final int action = event.getAction();
        
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    if (mPerformClick == null) {
                        mPerformClick = new PerformClick();
                    }
                    if (!post(mPerformClick)) {
                        performClickInternal();
                    }
                    break;
            }
             return true;
        }
        return false;
    }
    
    private boolean performClickInternal() {
        // Must notify autofill manager before performing the click actions to avoid scenarios where
        // the app has a click listener that changes the state of views the autofill service might
        // be interested on.
        notifyAutofillManagerOnClick();
    
        return performClick();
    }
    
    public boolean performClick() {
        //如果view设置了click事件,那么onClick()方法就会被执行
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            //调用点击事件
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }
        return result;
    }
    

    View的CLICKABLELONG_CLICKABLE可以通过setClickable()、setLongClickable()方法来设置,也可以通过View的setOnClickListener()、setOnLongClickListener()来设置,他们会自动把View设置为CLICKABLELONG_CLICKABLE

    这里其实就解释了,为什么我们OnTouchListener里面返回false的时候,因为View的onClickListener是在OnTouch.UP后面才调用的

    3)覆写onTouchEvent()

    当onTouchEvent返回true后,这个方法就没有去调用super.onTouchEvent()方法,View内部的onTouchEvent()方法就不能得到执行,就不能去调用performClick()方法,那么li.mOnClickListener.onClick(this);就不能执行。所以onClickListener就不能得到执行

    所以View内部的优先级:enable > onTouch > onTouchEvent > onClick

    2. View事件分发的传递规则

    上面的一连串过程,我们可以归纳为几行伪代码

    public boolean dispatchTouchEvent(MotionEvent event) {
        boolean result = false;
        //onInterceptTouchEvent默认返回false,如果我们自定义ViewGroup时,重写了这个方法,让他返回true,那么我们就能拦截点击事件
        if(onInterceptTouchEvent(ev)){
            //拦截点击事件后,开始在自己内部分发点击事件
            result = onTouchEvent();
        }else{
            //不拦截点击事件,交由子View去处理,这个步骤重复下去,最终会调用最底层的View,View是没有子View的,所以最后就会调用View的dispatchTouchEvent()方法,一般情况下,最终会调用最底层View的onTouchEvent()方法
            result = child.dispatchTouchEvent(ev);
        }
        return result;
    }
    

    事件从Activity ==> PhoneWindow => DecorView => ViewGroup ==> .... ==> View

    如果最顶层的ViewGroup开始,一直不拦截事件,事件最终会传递到最底层的View上面去,由于底层View,没有子View了,所以会调用View的onTouch方法。这就是事件由上向下传递

    如果底层的View不处理这个事件,就会让自己的onTouchEvent返回false,从这里开始,事件开始向上传递,如果中途的ViewGroup也不处理这个事件,最终就会传递到顶层的ViewGroup,由顶层的ViewGroup去处理这个事件。

    相关文章

      网友评论

          本文标题:【Android源码】View的事件分发

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