View·dispatchTouchEvent 源码分析(四)

上节概述

从上节View·dispatchTouchEvent 源码分析(三)中,我们分析了 ACTION_DOWN 事件的派发和拦截过程。

接下去,我们分析后续的事件是怎么被处理的!

正文

1、 InputEvent 事件传入 ViewRootImpl 中的 ViewPostImeInputStage方法中。
2、ViewPostImeInputStage 调用 processPointerEvent 方法处理 InputEvent 事件。
3、 在processPointerEvent内部调用是 mView.dispatchPointerEvent(event);,将事件进行处理。

// ViewPostImeInputStage.class
 private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;

            mAttachInfo.mUnbufferedDispatchRequested = false;
            boolean handled = mView.dispatchPointerEvent(event);
            if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
                mUnbufferedInputDispatch = true;
                if (mConsumeBatchedInputScheduled) {
                    scheduleConsumeBatchedInputImmediately();
                }
            }
            return handled ? FINISH_HANDLED : FORWARD;
        }

4、mView代表DecorView对象 (DecorViewPhoneWindow的内部类),而DecorView并未覆写dispatchPointerEvent方法。所以追述到View中。

// View.java
public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

5、此处我们只分析是触摸事件的情况,所以程序会走分支return dispatchTouchEvent(event);。此处会涉及到dispatchTouchEvent()方法的继承关系,所以有必要弄清楚mView的继承链。

// PhoneWindow#DecorView.class
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
    // ... 省略...
}

6、首先关注下DecorView、FrameLayout是否覆写dispatchTouchEvent()方法,检查后发现并未覆写。所以dispatchTouchEvent()方法的分析,被限定在View、ViewGroup的范围内。

7、因为当前的实例是DecorView,所以类型是ViewGroup。所以关注下ViewGroupdispatchTouchEvent()方法。因为 ACTION_DOWN事件已经在前一章被处理过了,所以我们跳过对ACTION_DOWN事件处理的代码片段。

// ViewGroup.class
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {

        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
              // 对 ACTION_DOWN 事件的处理
              // 构造 TouchTarget 链
              // 都忽略
        }

            // 分发 TOUCH 事件
            if (mFirstTouchTarget == null) {
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // 训练 TouchTarget 的链,执行事件派发
                // 【代码贴在第8小节里面】
            }
}

8、之前DecorView的实例在处理ACTION_DOWN事件时,mFirstTouchTarget已经被赋值了。所以mFirstTouchTarget代表当前获取焦点的视图(或被代码拦截的视图),作为事件的处理源头开始执行回朔操作。

TouchTarget predecessor = null; // 前任节点
TouchTarget target = mFirstTouchTarget;// 当前获得焦点的那个节点
    while (target != null) {
       final TouchTarget next = target.next;
       if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
           handled = true;
       } else {

           final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
          // 真正的执行事件分发
          if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                   handled = true;
          }
          // 当视图被 detach 的时候,即回收 TouchTarget 
          if (cancelChild) {
                if (predecessor == null) {
                       mFirstTouchTarget = next;
                 } else {
                       predecessor.next = next;
                 }
                 target.recycle();
                 target = next;
                 continue;
       }
}
predecessor = target;
target = next;
}

9、首次调用dispatchTouchEvent处理 ACTION_DOWN事件,获得的TouchTarget 链的过程。经过dispatchTouchEvent的处理,得到了如下图的TouchTarget 链—— 3、2、1,同时使用mFirstTouchTarget 指向3这个位置

ACTION_DOWN事件的派发

10、确定了3、2、1这条链之后,在第8点中就能直接对这条链进行事件派发,派发顺序是3、2、1

ACTION_DOWN 后续事件的派发

11、最后的事件派发的一根稻草,被定位在ViewGroup#dispatchTransformedTouchEvent()方法上。

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        //  检测是否需要发送ACTION_CANCEL。
        //  如果cancel为true 或者 action是ACTION_CANCEL;
        //  则设置消息为ACTION_CANCEL,并将ACTION_CANCEL消息分发给对应的对象,并返回。
        // (01) 如果child是空,则将ACTION_CANCEL消息分发给当前ViewGroup;
        //      只不过会将ViewGroup看作它的父类View,调用View的dispatchTouchEvent()接口。
        // (02) 如果child不是空,调用child的dispatchTouchEvent()。
        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

        // 计算新旧的手指数目
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        // 如果产生的新手指数目与原先的数目不一致,则不消费此次事件。
        if (newPointerIdBits == 0) {
            return false;
        }

        // 如果手指的数量是相同的,我们不需要执行任何花哨的不可逆转换, 
        // 如果我们想重用分派的事件,需要谨慎地还原任何我们做的更改。
        // 否则,我们需要做一个副本。
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

        // 如果手指数目不一致,则意味着我们需要先转化 MotionEvent 事件。
        // 然后在对转换后的对象进行事件派发
        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());
            }

            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }

12、dispatchTransformedTouchEvent()会对触摸事件进行重新打包后再分发。如果它的第三个参数child是null,则会将触摸消息分发给ViewGroup自己,只不过此时是将ViewGroup看作一个View,即调用View的dispatchTouchEvent()进行消息分发。

小结

贴了两张(巨丑)手绘的图,大致描述了事件“分发与回朔”的过程。这一章没有涉及到事件分发的应用技巧,而是对前面两章的小结。借这几篇文章,将事件产生、分发、回朔的流程都吃透了。那么接下去梳理具体的事件分发的应用时,就会轻松不少。

下一章将会分析View在调用dispatchTouchEvent()时会影响哪些方法,并且这些影响的方法如何在实际工作中产生效用。

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

推荐阅读更多精彩内容