Android View事件分发机制总结

一、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不会被赋值。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,015评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,262评论 1 292
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,727评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,986评论 0 205
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,363评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,610评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,871评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,582评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,297评论 1 242
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,551评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,053评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,385评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,035评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,079评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,841评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,648评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,550评论 2 270

推荐阅读更多精彩内容