美文网首页
Android View事件分发机制总结

Android View事件分发机制总结

作者: Android_ZzT | 来源:发表于2019-10-14 00:13 被阅读0次
    一、MotionEvent
    • DOWN -> MOVE (多次) -> UP 是一个完整的动作序列

    • 补充:

      ACTION_CANCEL 已经废弃不用,可当做 ACTION_UP 处理,源码如下

        /**
         * Constant for {@link #getActionMasked}: The current gesture has been aborted.
         * You will not receive any more points in it.  You should treat this as
         * an up event, but not perform any action that you normally would.
         */
        public static final int ACTION_CANCEL = 3;
    
    二、分发过程重要源码解析

    Activity 分发逻辑核心代码

    class Activity {
    
        //如果所有子 view 都不消费事件,则通过 Activity#onTouchEvent 消费
        public boolean dispatchTouchEvent(MotionEvent ev) {
            if (getWindow().superDispatchTouchEvent(ev)) { // PhoneWindow#superDispatchTouchEvent -> DecorView#superDispatchTouchEvent -> FrameLayout#dispatchTouchEvent -> ViewGroup#dispatchTouchEvent
                return true;
            } else {
                return onTouchEvent(ev);
            }
        }
    }
    

    ViewGroup 分发逻辑核心代码

    class ViewGroup extends View implements ViewParent {
        
        public boolean dispatchTouchEvent(MotionEvent ev) {
                boolean handled = false;
     
                // 1. ACTION_DOWN -> 事件序列的开始,重置到初始状态.
                if (actionMasked == MotionEvent.ACTION_DOWN) {
                    cancelAndClearTouchTargets(ev); //重置 mFirstTouchTarget = null
                    resetTouchState(); //重置 touch state,最主要的一点就是重置 FLAG_DISALLOW_INTERCEPT 状态,也就决定了 child 无法阻止 parent 拦截 DOWN 事件
                }
                
                // 2. Check for interception.
                final boolean intercepted;
                if (actionMasked == MotionEvent.ACTION_DOWN //点下或mFirstTouchTarget != null检查是否需要拦截
                        || 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 {// 当没有触摸targets,且不是down事件时,开始持续拦截触摸。
                    intercepted = true;
                }
                
                // 3. 如果不取消,不拦截,则分发事件
                if (!cancel && !intercepted) {
                    for (int j = childrenCount; j>=0 ; j--) { //由上层到下层遍历
                    View child = mChildren[index];
                    
                    if (!canReceiveEvent(child))
                        break; //如果 child 不能接收事件,跳出循环,不能接收事件的条件为,事件坐标不在 child 范围,或 child 正在执行动画
                    
                    if (dispatchTransformedTouchEvent(child) { //如果 child 消费了事件
                        addTouchTarget(); //赋值 mFirstTouchTarget
                        break; //跳出循环
                    }
                    
                    if (mFirstTouchTarget == null) { //事件传递到最后一个child,并且child是ViewGroup,或者ViewGroup拦截了事件,mFirstTouchTarget 一直都不会被赋值,所以后续的所有事件都会被ViewGroup处理
                        handled = dispatchTransformedTouchEvent(null);
                    }
                }
                
                // 4.返回结果,如果是 false,需要传到 activity 处理
                return handled;
        }
        
        private boolean dispatchTransformedTouchEvent(View child) {
            if (child == null) { // child is null, call View#dispatchTouchEvent
                return super.dispatchTouchEvent();
            } else {
                return child.dispatchTouchEvent();
            }
        }
    }
    

    View 的分发核心逻辑

    class View {
        public boolean dispatchTouchEvent(MotionEvent ev) {
            boolean result;
            
            if (mOnTouchListener != null && mOnTouchListener.onTouch()) {//OnTouchListener优先级较高
                result = true;
            }
            
            if (!result && onTouchEvent(ev)) {
                result = true;
            }
            
            return result;
        }
        
        public boolean onTouchEvent(MotionEvent ev) {
            if (clickable || longClickable) {
                switch(ev.getAction) {
                    // ... 处理各种事件的逻辑,比如一次点击后会触发 performClick -> clickListener.onClick(),完成了最熟悉的点击监听回调
                }
                
                return true;
            }
        }
    }
    
    小结
    1. 事件传递到附属在Activity的Window上,然后Window将事件交给DecorView(FrameLayout)处理。

    2. DecorView也是ViewGroup,事件传递到ViewGroup中。

    3. 首先进入ViewGroup的dispatchTouchEvent()中,然后进入onInterceptTouchEvent(),如果返回true,表示需要拦截此次事件,则会执行ViewGroup的onTouchEvent()。如果返回false,表示不需要拦截事件,则会从上层到下层遍历子View并进入dispatchTransformedTouchEvent(child)。

    4. dispatchTransformedTouchEvent()会判断传入childView是否为null,如果为null,则之行super.dispatchTouchEvent(),即执行View的事件分发。如果不为null,则会执行childView的事件分发。

    5. 事件最终总会传到一个 View 或 ViewGroup,需要相对应的 onTouchEvent()处理后的返回值,如果返回true,表示事件已经处理,则ViewGroup会break对子View的遍历并会给FirstTouchTarget赋值。如果返回false,表示事件没有处理,就继续遍历其他子View进行处理,如果所有子View处理都返回了false,就会执行dispatchTransformedTouchEvent(null),也就是执行super.dispatchTouchEvent(),也就是View的事件分发。

    6. 事件传递到View的dispatchTouchEvent(),首先会进入mOnTouchListenner.onTouch(),如果进入onTouch()且返回true,则dispatchTouchEvent()返回true,表示事件已经消费。如果没进入或返回false,则继续执行onTouchEvent(),在此方法中会处理相应的事件,如onClick。

    7. 处理事件后,只要View满足clickable or longClickble 那么onTouchEvent()都会返回true(View是disable的也无所谓)。如果返回false表示事件没能处理,则会交给父级View处理,如果一直都返回false,则会回到Activity的onTouchEvent()处理事件。

    补充:onInterceptTouchEvent()执行前提,ACTION_DOWN || mFirstTouchTarget != null。也就是说DOWN事件一定会进入拦截方法中(child#requestDisAllowIntercept也没用),而且一旦拦截后,之后的一系列事件都会交给此控件处理,因为mFirstTouchTarget不会被赋值。

    相关文章

      网友评论

          本文标题:Android View事件分发机制总结

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